Skip to content

Update and Add OpenProject integration #34028

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

theofficialvedantjoshi
Copy link
Collaborator

Fixes: #29944

Screenshots and screen captures:

image

Self-review checklist
  • Self-reviewed the changes for clarity and maintainability
    (variable names, code reuse, readability, etc.).

Communicate decisions, questions, and potential concerns.

  • Explains differences from previous plans (e.g., issue description).
  • Highlights technical choices and bugs encountered.
  • Calls out remaining decisions and concerns.
  • Automated tests verify logic where appropriate.

Individual commits are ready for review (see commit discipline).

  • Each commit is a coherent idea.
  • Commit message(s) explain reasoning and motivation for changes.

Completed manual review and testing of the following:

  • Visual appearance of the changes.
  • Responsiveness and internationalization.
  • Strings and tooltips.
  • End-to-end functionality of buttons, interactions and flows.
  • Corner cases, error conditions, and easily imagined bugs.

@zulipbot
Copy link
Member

Hello @zulip/server-integrations members, this pull request was labeled with the "area: integrations" label, so you may want to check it out!

@theofficialvedantjoshi
Copy link
Collaborator Author

@Niloth-p

This PR fixes the issues pointed out in #29944.

Write a fresh view.py:

  • Created templates for each message to be sent for clarity.
  • Removed the wildvalue type and the initial logic inside the api_openproject_webhook.
  • Fetching the action from the payload and handling each case separately.
  • Improved error handling.

Other changes:

  • Updated the expected_message in tests to the new templates.
  • Updated the screenshot and added a realistic project name.
  • Updated doc.md to improve the punctuation and language for clearer instructions.

Do review this PR and let me know if any changes have to be made.

@Niloth-p
Copy link
Collaborator

Niloth-p commented Mar 17, 2025

Thank you for the updates. They are good improvements.

When I mentioned writing a "fresh" view.py, I didn't want the next contributor taking any inspiration from the previous view.py.
We do use JsonBodyPayload[WildValue], not JsonBodyPayload[dict[str, Any]].
I feel like the different cases in the view function can be optimized a bit further. Could you see if that's possible? For example, instead of assigning the topic manually in each case, it seems to be possible to programmatically generate it in common. We seem to be raising UnsupportedWebhookEventTypeErrors more than usual.

Can you please share the info you gathered or relevant links regarding the default values of parameters like "work package type", "action status", etc.?

Use ./tools/screenshots/generate-integration-docs-screenshot --integration integrationnamefor taking a screenshot.

Reminder: https://zulip.readthedocs.io/en/latest/contributing/commit-discipline.html
I think it's fine to have the other contributor's commit separately for the moment, so that the diffs are viewable, but we'll be squashing before merging. Please do squash your own commits.

I'm wondering if we could drop the "successfully" word, given that it's part of all of the message templates.

You also need to ensure coverage - see the failing checks.

Ah, I'm seeing that you've noticed some of these yourself.
This happened in the Google Calendar PR too, where you tagged me asking for reviews and questions on clearing the tests a couple of times, and then ended up solving them yourself in a few hours, and then you deleted some of those PR comments. Please try taking a bit more time to attempt solutions and doing a self-review before tagging reviewers next time.

@theofficialvedantjoshi theofficialvedantjoshi force-pushed the open-project-integration branch 2 times, most recently from cd0d994 to eb29f36 Compare March 17, 2025 13:28
@theofficialvedantjoshi
Copy link
Collaborator Author

I have made the necessary fixes.

Redundant error handling has been removed. The outer try and except is added to check if any django validation error occurs while accessing keys in the WildValue payload and throws an AnomalousWebhookPayloadError error. The "successfully" word has been removed from all messages. topic is now being programmatically generated, do point out any other possible optimizations. The screenshot has been generated using ./tools/screenshots/generate-integration-docs-screenshot --integration integrationname. I have also squashed all my older commits into a single commit.

Sources for EVENTS were found from the Webhooks page inside an OpenProject page.
image

Sources for WORKPACKAGE_TYPES(TASK, MILESTONE, PHASE) were found from the work packages page inside an OpenProject Project.
image

@theofficialvedantjoshi theofficialvedantjoshi force-pushed the open-project-integration branch 2 times, most recently from 0f68aeb to bcf9d6a Compare March 19, 2025 11:17
@theofficialvedantjoshi
Copy link
Collaborator Author

@Niloth-p are there any pending issues or is this pr ready to be merged?

@Niloth-p
Copy link
Collaborator

It's not ready to be merged, there are several rounds of review still pending.

@theofficialvedantjoshi
Copy link
Collaborator Author

theofficialvedantjoshi commented Mar 26, 2025

@Niloth-p Any updates on what needs to be added or fixed for this integration?

Copy link
Collaborator

@Niloth-p Niloth-p left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This version definitely looks much improved. It's so much more easy to read now. Thank you for working on this!

I've left code suggestions for how the code can be optimized. Please verify the payload fields, and share the info and/or links that you find. The topic value needs to be re-considered.

Use git rebase not git merge. It's fine in this case, since we'll be squashing anyways. But, please read https://zulip.readthedocs.io/en/latest/contributing/continuing-unfinished-work.html

from zerver.lib.webhooks.common import check_send_webhook_message
from zerver.models import UserProfile

EVENTS: dict[str, str] = {
Copy link
Collaborator

@Niloth-p Niloth-p Mar 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace lines 12-43. That's way too verbose, and not optimized.

Suggested change
EVENTS: dict[str, str] = {
ALL_EVENT_TYPES: list[str] = [
"project:created",
"project:updated",
"work_package:created",
"work_package:updated",
"time_entry:created",
"attachment:created",
]
WORKPACKAGE_TYPES: list[str] = ["Task", "Milestone", "Phase"]
PROJECT_MESSAGE_TEMPLATE = "Project **{name}** was {action}."
WORK_PACKAGE_MESSAGE_TEMPLATE = "**{type}** work package **{subject}** was {action}."
ATTATCHMENT_MESSAGE_TEMPLATE = "File **{filename}** was uploaded."
TIME_ENTRY_MESSAGE_TEMPLATE = "A time entry of **{hours}** was {action} for project **{project}**."
  1. There's no reason to use an events dict.
  2. Do not use commas at the end of a list or dict, they force un-wrap it.
  3. Use normal quotes, not triple quotes for the templates. Unless you believe that there's a possibility for any of them to have quote characters inside the template.
  4. I've edited a couple templates.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes this is a much better approach and has been added.

payload: JsonBodyPayload[WildValue],
) -> HttpResponse:
try:
action: str = payload["action"].tame(check_string)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace lines 55-59 with

Suggested change
action: str = payload["action"].tame(check_string)
event_type: str = payload["action"].tame(check_string)
item, action = event_type.split(":")
action_data: WildValue = payload[item]
  1. Use split to get the values. Using the dict for that is just redundancy.
  2. Rename the variables like above.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes this is a better approach and has been added.

action_status: str = action.split(":")[1]

action_data: WildValue = payload[EVENTS[action]]
topic: str = EVENTS[action].replace("_", " ").title()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't make sense.
Why would we want to use "work package", "attachment", or "project" as Zulip topics?
Think from the POV of the user. How would you want your notifications to be grouped?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the perspective of the user and looking at other integrations, I removed this and set the topic according to the openproject project name. Since the project name is available differently in all payloads the topic is dynamically defined in each event.


check_send_webhook_message(request, user_profile, topic, message)
return json_success(request)
except ValidationError:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not how we do the validation.
You must have noticed that we don't have the entire functions inside a try-except block for all the webhook integrations.

You need to check the type of the fields returned in the payload. This is what I was asking when I asked for the "default values" in the last review round. Not the types of work packages or such, but the default value, for example, could it be an empty string, undefined or null? Are the fields mandatory or optional?

For example, you're parsing project like this - project=action_data["_embedded"]["project"]["name"].tame(check_string), but did you verify that action_data will always have those nested fields? I do think that it's likely that it will, but one needs to still verify that, and check for any edge cases.

We need to verify the type of each payload parameter that we parse from their API docs or their YAML, if the docs don't have it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This try - except has been removed. There is very minimal documentation available on specific json data. After manual testing I believe these keys will always exist and no validation error would occur. Also since we are showing very minimal surface level notifications this should be good to go.

action_status=action_status,
)
case "attachment":
message = ATTATCHMENT_MESSAGE_TEMPLATE.format(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a typo.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved.

Copy link
Collaborator

@Niloth-p Niloth-p left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for making the changes as suggested, and quickly!

I've left some suggestions on the doc that you can absorb.

Great, we're almost there. Let's do another round on the message templates, now that the topic is set as the project name.

  • We don't have to include the project name in the time-entry message template, as it's already part of the topic.
  • Would it make sense to add the work package name to the attachment and time entry message templates?
  • We don't seem to have the user being mentioned in any of the message templates? I think OpenProject is actually designed for teams, right, so shouldn't that be included to know who did what? Can you see for which of the templates it makes sense to include the user name? Because we don't want to sound too repetitive either.
  • Did you check if OpenProject includes (hashed) links to the file attachments in the payload? If they do, we could format the file attachments like this.
    img
  • It seems like the time entry includes a link too? Can you check that? We should be including all such links.
  • When a project is created, we'd also want to show fields like the description, and mention parent project names, if they're nested.

Using these as inspiration, can you please take a look at all the fixtures and their fields once to identify details that we'd want to include in our messages?
And if they do, format them nicely into a readable sentence (as opposed to key-value pairs).

@@ -0,0 +1,33 @@
# Zulip Open Project integration
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Zulip Open Project integration
# Zulip OpenProject integration

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

@@ -0,0 +1,33 @@
# Zulip Open Project integration

Get Zulip notifications for new events on your **Open Project** page.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Get Zulip notifications for new events on your **Open Project** page.
Get Zulip notifications for your **OpenProject** activities!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.


1. {!generate-webhook-url-basic.md!}

1. Sign in to OpenProject and create your organization.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lines 11-19

Suggested change
1. Sign in to OpenProject and create your organization.
1. From your OpenProject organization, click on your user profile icon,
choose **Administration**, select **API and Webhooks**, and navigate to
the **Webhooks** tab from the left panel.
1. Click on **+ Webhook**. Enter a name of your choice for the webhook,
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**.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.


{!webhooks-url-specification.md!}

[1]: https://www.openproject.org/docs/system-admin-guide/api-and-webhooks/
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
[1]: https://www.openproject.org/docs/system-admin-guide/api-and-webhooks/
[1]: https://www.openproject.org/docs/system-admin-guide/api-and-webhooks/#webhooks

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

@theofficialvedantjoshi
Copy link
Collaborator Author

Thanks a lot for your review!

I have looked at the fixtures and added the following fields:

  • Parent project name in project_created and project_updated.
  • Authors included in workpackage, attachment and time entry events.
  • Container type and name for attachments.

Reasons and supporting docs for external checks on optional fields:

  • Parent Projects (can be null)
    image
  • Workpackage in time entries (can be null)
    image
  • Container types for attachments can be workpackage, message, meeting etc.

Updated Tests:

  • Adding new checks also meant creating separate tests for these scenarios.
  • Essentially modified existing fixtures to create new fixtures to replicate missing fields.

Reasons for not adding additional fields mentioned in the review:

  • Hashed links in File attachments: Couldn't find this in the fixtures or docs.
  • Description for projects: This involves adding multiple checks and formats. Also there are no character constraints mentioned for this in the docs so if the description is large truncating it will be an issue.

If there is a better way to format message template sentences with newly added fields do let me know.

Copy link
Collaborator

@Niloth-p Niloth-p left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for checking all of them, and adding all those new fixtures and tests. That's quite helpful.
Could you please rename the fixtures like this - "project_created__with_parent" and so on, in the form of "{item}_{action}__{suffix}". That's the convention we typically use.
Ah, yes, this table's constraints column is what I was asking for previously. Kudos on locating it! And thanks for sharing the screenshots. Could you share the URL as well?

TIME_ENTRY_MESSAGE_TEMPLATE = "A time entry of **{hours}** was {action} by **{user}**."

TIME_ENTRY_WITH_WORKPACKAGE_MESSAGE_TEMPLATE = (
"A time entry of **{hours}** was {action} by **{user}** for workpackage **{workpackage}**."
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about

Suggested change
"A time entry of **{hours}** was {action} by **{user}** for workpackage **{workpackage}**."
"**{user}** {action} a time entry of **{hours}** for **{workpackage}**."

I've removed the "workproject" because it seems the time entries can only be for workprojects or projects, so it's implicitly known.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is better.

user=action_data["_embedded"]["user"]["name"].tame(check_string),
)
)
topic = action_data["_embedded"]["project"]["name"].tame(check_string)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's interesting that the image you linked says that the project could be different from the workpackage's project, if the workpackage is moved. I think we should send the notifications to the workpackage's project then, if it's mentioned. What do you think?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no project linked in the workpackage object in the time entry payload. The only project we have is the one mentioned in the image. To fetch the workpackages project we would have to call other apis which will complicate things. We can let this be or make the topic name in case of time entries be workpackage names?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean that the work package does not have an associated project name in the payload that we have, or in all payloads? Because the doc sounded like it would be included at least if the work package was moved to another project?

Oh, we don't want to make the topic the work package's name. The project name is fine.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't find any sample payload in the docs that includes a work package project when fetching a time entry. This scenario might need to be replicated manually but my OpenProject trial has expired.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe when there's no workpackage, we add "for this project" to the message? Hmmm... I guess if they've set the topic in the webhook URL, then that would be misleading. Their docs do say that the project will be not null for a time entry: https://www.openproject.org/docs/api/endpoints/time-entries/#linked-properties.

I think that if there's no work package, then adding the project name to the message makes sense ...

Current:

Nirved Mishra logged 1 hour.

Revised:

Nirved Mishra logged 1 hour on Project1.

That way if the topic is set by the bot's URL, there's still information about where the time entry was logged.

message = (
TIME_ENTRY_WITH_WORKPACKAGE_MESSAGE_TEMPLATE.format(
hours=action_data["hours"].tame(check_string).split("T")[1],
project=action_data["_embedded"]["project"]["name"].tame(check_string),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to no longer be used?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, project has been removed.

)

ATTACHMENT_MESSAGE_TEMPLATE = (
"File **{filename}** was uploaded by **{author}** in {container_type} **{container_name}**."
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about

Suggested change
"File **{filename}** was uploaded by **{author}** in {container_type} **{container_name}**."
"**{author}** uploaded **{filename}** in {container_type} **{container_name}**."

I assume that you're sure that the container is not null.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is much better.

PROJECT_MESSAGE_TEMPLATE = "Project **{name}** was {action}."

PROJECT_WITH_PARENT_MESSAGE_TEMPLATE = (
"Project **{name}** was {action} in project **{parent_project}**."
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to show the parent project name everytime a project is updated? I think it's fine just to mention it when we're creating the project.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed parent project for updated event.


PROJECT_MESSAGE_TEMPLATE = "Project **{name}** was {action}."

PROJECT_WITH_PARENT_MESSAGE_TEMPLATE = (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Instead of having a separate PROJECT_WITH_PARENT_MESSAGE_TEMPLATE, we can just pass in an extra parameter parent to PROJECT_MESSAGE_TEMPLATE with the value " in project **{parent_project}**." or "" if no parent.
  • Do the same as above for TIME_ENTRY_WITH_WORKPACKAGE_MESSAGE_TEMPLATE, overload it onto the TIME_ENTRY_MESSAGE_TEMPLATE.

This would significantly simplify the view.py code logic.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this will make the code cleaner. I have reverted to older template names and updated conditionals.

@theofficialvedantjoshi theofficialvedantjoshi force-pushed the open-project-integration branch 2 times, most recently from a0f43c6 to cd039f4 Compare April 1, 2025 13:55
@theofficialvedantjoshi
Copy link
Collaborator Author

Thanks a lot for your review!

I have made necessary changes to the the template sentences and renamed fixtures to follow proper convention. Also cleaned the conditionals and stuck to single templates for categories.

Other relevant information from the docs:

  • Endpoints: This contains information on various objects that we receive events for.
  • Containers being not null:
    image

Copy link
Collaborator

@Niloth-p Niloth-p left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the updates!
Apart from some very minor comments, this LGTM.
You can go ahead and squash the commits. Remember to add co-author credits for the previous author, and it would be nice if you could add me too.

case "project":
parent_project: str = (
f" in project **{action_data['_embedded']['parent']['name'].tame(check_string)}**"
if "_embedded" in action_data
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel this parent project computation can be simplified by using .get() instead of square brackets.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is better and has been added.

user=action_data["_embedded"]["user"]["name"].tame(check_string),
)
)
topic = action_data["_embedded"]["project"]["name"].tame(check_string)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean that the work package does not have an associated project name in the payload that we have, or in all payloads? Because the doc sounded like it would be included at least if the work package was moved to another project?

Oh, we don't want to make the topic the work package's name. The project name is fine.

"attachment_created",
expected_topic,
expected_message,
content_type="application/json",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm on mobile and I can't check right now, but isn't the default value of content_type json for check_webhook? If so, let's remove this line and the trailing comma from all these tests.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, content_type="application/json" was the default value and has been removed.

@theofficialvedantjoshi theofficialvedantjoshi force-pushed the open-project-integration branch 2 times, most recently from 4c236c1 to 5b25b74 Compare April 1, 2025 19:30
@theofficialvedantjoshi
Copy link
Collaborator Author

Thanks again for your help with this issue!

I've added the co-author credits and squashed the commits.

Copy link
Collaborator

@Niloth-p Niloth-p left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for working on this integration!
Once you're done with this update, add the "maintainer review" label using zulipbot, and tag Lauryn Menard requesting a review.

@theofficialvedantjoshi
Copy link
Collaborator Author

@zulipbot add "maintainer review"

@zulipbot zulipbot added the maintainer review PR is ready for review by Zulip maintainers. label Apr 4, 2025
@theofficialvedantjoshi
Copy link
Collaborator Author

@laurynmm could you please review this PR?

Copy link
Collaborator

@laurynmm laurynmm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@theofficialvedantjoshi - Thanks for working on this integration! I had some comments about the Zulip message formats for the different events that we're handling.

Let me know if you have any questions about my feedback comments below!

"attachment:created",
]

WORKPACKAGE_TYPES: list[str] = ["Task", "Milestone", "Phase"]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, this isn't used. I assume because these types are can be created and deleted and therefore we can't assume what these strings are for all OpenProject integrations: https://www.openproject.org/docs/system-admin-guide/manage-work-packages/work-package-types/.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was overlooked and has been removed.

.tame(check_string)
)
if parent_project and action == "created":
parent_project_message = f" in project **{parent_project}**"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might revise this to " as a sub-project of **{parent_project}**" since that's a bit clearer in the hierarchy that's being created in the projects here ... https://www.openproject.org/docs/user-guide/projects/#project-structure.

Current:

"Project AI Backend was created in project Demo project."

Revised:

Project AI Backend was created as a sub-project of Demo project.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the changed template is clear and has been added.

PROJECT_MESSAGE_TEMPLATE = "Project **{name}** was {action}{parent_project_message}."

WORK_PACKAGE_MESSAGE_TEMPLATE = (
"**{type}** work package **{subject}** was {action} by **{author}**."
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might revise this to "**{subject}** (work package **{type}**) was {action} by **{author}**".

Current:

Task work package Draft project description was created by Nirved Mishra.

Revised:

Draft project description (work package Task) was created by Nirved Mishra.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

)

ATTACHMENT_MESSAGE_TEMPLATE = (
"**{author}** uploaded **{filename}** in {container_type} **{container_name}**."
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that in the generated fixture for this type of event, the container_type renders to "workpackage", which looks a bit odd since their docs and our other message templates use "work package". It looks like the type is in camel case ...

        "container": {
          "_type": "WorkPackage",

Maybe we just omit that and have the name of the container?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we can ignore this since adding an extra function to format camel case to regular case is unnecessary.

)

TIME_ENTRY_MESSAGE_TEMPLATE = (
"**{user}** {action} a time entry of **{hours}**{workpackage_message}."
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the hours here looks to be the duration string of ISO 8601 (https://en.wikipedia.org/wiki/ISO_8601#Durations); see https://www.openproject.org/docs/api/endpoints/time-entries/#local-properties and https://www.openproject.org/docs/api/basic-objects/#dates-times-durations-and-timestamps.

It'd be nice to have this time duration be more readable in the Zulip message as it's not super clear that the below is noting that Nirved logged spending 1 hour of time on the work package task.

Current:

Nirved Mishra created a time entry of 1H for Create project timeline.

Maybe revise to something like ...

Nirved Mishra logged 1 hour on Create project timeline.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes created a function that uses regex to parse the iso duration to human readable time.

Copy link
Collaborator

@Niloth-p Niloth-p left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, @theofficialvedantjoshi, quick reminder to use rebase instead of merge.
I've requested you to look into the rebase workflow twice before, please make sure to check it out, and stick to it.

Note: Though I've left a suggestion, I have not actually reviewed or verified any of the changes.

if duration is None: # nocoverage
raise ValueError(f"Invalid ISO 8601 duration format: {iso_duration}")

days = int(duration.group("days") or 0)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lines 109-122 look redundant to me.
Can you check if something like the below works instead?

Suggested change
days = int(duration.group("days") or 0)
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)
]

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes this works just fine. I thought my code would be better for readability but this is much more concise.

@theofficialvedantjoshi theofficialvedantjoshi force-pushed the open-project-integration branch 2 times, most recently from 9915117 to c606e72 Compare April 25, 2025 09:01
@theofficialvedantjoshi
Copy link
Collaborator Author

@Niloth-p Thanks for your review.
I accidentally resolved the merge conflicts on github and made a merge commit. I've reverted, rebased and resolved conflicts.
This will not happen again. I've made the necessary changes and this integration should be good to go.

Copy link
Collaborator

@laurynmm laurynmm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@theofficialvedantjoshi - Thanks for making the updates for the time entry duration. I think we can improve those event messages a bit more. Let me know if you have any questions about my comments!

user=action_data["_embedded"]["user"]["name"].tame(check_string),
)
)
topic = action_data["_embedded"]["project"]["name"].tame(check_string)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe when there's no workpackage, we add "for this project" to the message? Hmmm... I guess if they've set the topic in the webhook URL, then that would be misleading. Their docs do say that the project will be not null for a time entry: https://www.openproject.org/docs/api/endpoints/time-entries/#linked-properties.

I think that if there's no work package, then adding the project name to the message makes sense ...

Current:

Nirved Mishra logged 1 hour.

Revised:

Nirved Mishra logged 1 hour on Project1.

That way if the topic is set by the bot's URL, there's still information about where the time entry was logged.

@theofficialvedantjoshi theofficialvedantjoshi force-pushed the open-project-integration branch 2 times, most recently from 18be545 to a727ad8 Compare May 2, 2025 19:01
Fixes zulip#29944.

Co-authored-by: theofficialvedantjoshi <[email protected]>
Co-authored-by: Niloth P <[email protected]>
@laurynmm laurynmm force-pushed the open-project-integration branch from a727ad8 to a3a9dca Compare May 5, 2025 16:09
@laurynmm
Copy link
Collaborator

laurynmm commented May 5, 2025

@theofficialvedantjoshi - Thanks for making those updates! I had a few small notes left, so I went ahead and updated the changes here for those, see below. I''m going to mark this as ready for integration review, though @Niloth-p feel free to comment on anything if you do see any

Updates:

  • Updated documentation instructions a bit, see screenshot below.
  • In the view code:
    • Changed the use of match/case to if/else, which is generally what we use for these incoming webhook views.
    • Added a final else clause in case the event item doesn't match one of the expected webhook events.
    • Moved the helper function for formatting the duration text to be before the main view function.
    • Adjusted some spacing and variable names.
  • In the commit message, I added @theofficialvedantjoshi as a co-author, since the author of the commit is listed as nirved mishra.

Notes:

  • For the formatted duration ValueError, I'm not sure if it's really necessary. It might be better to just have that function return the time string with the bold formatting and in the case of an unexpected duration format the simplified string without the bold formatting.

Updated doc screenshot:

Screenshot from 2025-05-05 17-47-56

@laurynmm laurynmm added integration review Added by maintainers when a PR may be ready for integration. and removed maintainer review PR is ready for review by Zulip maintainers. labels May 5, 2025
@laurynmm laurynmm removed their request for review May 5, 2025 16:20
@timabbott
Copy link
Member

Changed the use of match/case to if/else, which is generally what we use for these incoming webhook views.

I think it's probably OK to use match/case in these; we weren't using that pattern before just because the feature wasn't available in all Python versions that we support until recently.

@timabbott timabbott merged commit 3fc6311 into zulip:main May 5, 2025
7 checks passed
@timabbott
Copy link
Member

Merged, thanks everyone for the effort on this!

@theofficialvedantjoshi
Copy link
Collaborator Author

theofficialvedantjoshi commented May 5, 2025

Thanks to everyone who worked on this. @Niloth-p @laurynmm thank you for your comprehensive reviews!

@theofficialvedantjoshi theofficialvedantjoshi deleted the open-project-integration branch May 6, 2025 04:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: integrations good first issue integration review Added by maintainers when a PR may be ready for integration. size: XL
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add OpenProject integration
6 participants