Skip to content
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

zulip-integrations: Update google oauth and reminders for google calendar integration. #850

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

theofficialvedantjoshi
Copy link

The oauth2client library used in the google-calendar integration scripts has become deprecated. This PR updates the script to replace oauth2client with other libraries that Google is now using, specifically google-auth-httplib2 and google-auth-oauthlib.

Fixes: #847

How did you test this PR?

  • Generated a client_secret.json file from a test Google Cloud application that uses the calendar API.
  • Verified the authentication process and the creation of google-credentials.json in the home directory.
  • Tested each flag in the argument parser by specifying the hostname and port.
  • Tested the no-local-sever-auth flow in the console after obtaining an authentication code from my application's redirect URL.
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.

@theofficialvedantjoshi theofficialvedantjoshi force-pushed the update-google-calendar-authentication branch from 2249012 to af1090e Compare January 8, 2025 18:23
@theofficialvedantjoshi
Copy link
Author

@timabbott could you please review this PR?

@alya
Copy link
Contributor

alya commented Jan 9, 2025

@Niloth-p It would be great to get your review on this one.

@theofficialvedantjoshi
Copy link
Author

@Niloth-p any updates?

@theofficialvedantjoshi
Copy link
Author

@timabbott @Niloth-p anything pending in this pr?

@Niloth-p
Copy link
Contributor

Hi, @theofficialvedantjoshi, apologies for the delay in getting back.

Thank you for the PR!
The new libraries definitely work.
Only on testing the integration, I discovered that the integration needs dozens more fixes and clean ups, apart from the library replacement, before it's ready for use. So, I have a local branch in progress, but it's unfortunately taking longer than expected, due to other priorities. I'll be absorbing this PR as part of that and give you co-author credits for this commit.

If you're interested in working more on this integration, it would be helpful if you could help improve the message being generated, by adding separate commit(s) on top of this PR. Here are some ways we can improve the message:

  1. Adding more details to the messages generated would be essential. It currently just sends <event_name> starts at <time>, that's it. We need to be adding more details to make the integration up to par with all the other notification modes, by using the below fields that Google Calendar provides:
    • the event description
    • the htmlLink - to the event in the Google Calendar Web UI
    • time the event ends
    • additional info fields when present - formatted into a sentence (Use other Google Calendar notifications for inspiration):
      • status
      • location
      • eventType
      • hangoutLink
      • organizer.displayName (if organizer.self is not set, i.e., the organizer is not the owner of the calendar)
  2. The script currently sends reminders only x minutes before, and x applies to all events. But, Google Calendar has a property reminders.overrides.minutes that tracks the duration before which the user has requested a notification for. This needs to be used whenever available.
  3. The script sends reminders on every single event. Google Calendar supports reminders.overrides to turn off notifications for particular events. We should too.
  4. Events without a title do not get a notification currently. It should instead create events with the title as "(No title)", like Google Calendar typically does.

@theofficialvedantjoshi
Copy link
Author

theofficialvedantjoshi commented Jan 30, 2025

Hi @Niloth-p, thanks for the review.

I am interested in working more on this integration. I'll set things up locally and add the features mentioned above.
Will keep updating this thread if I have any doubts.

I am also interested in participating in GSoC 2025 with zulip. Will you be mentoring this time? Will contributions to this repository add value to my proposal?

@Niloth-p
Copy link
Contributor

Glad to hear that.
Questions like these are better discussed and answered in https://chat.zulip.org/.

Contributions to this repo do count as much as any other Zulip repo.
But, have you taken a look at the proposed project ideas? In general, it would help to have some contributions in the same part of the codebase as the project you plan to propose for.

Though I anticipate being involved with any GSoC contributors working on Zulip integrations, and the set of integrations here in this repo could do with a lot of improvements, the priorities of GSoC contributions and project proposals is not my domain, the GSoC channel is a much better place to get info regarding that.

@zulipbot
Copy link
Member

zulipbot commented Feb 6, 2025

Hello @theofficialvedantjoshi, it seems like you have referenced #847 in your pull request description, but you have not referenced them in your commit message description(s). Referencing an issue in a commit message automatically closes the corresponding issue when the commit is merged, which makes the issue tracker easier to manage.

Please run git commit --amend in your command line client to amend your commit message description with Fixes #847..

An example of a correctly-formatted commit:

commit fabd5e450374c8dde65ec35f02140383940fe146
Author: zulipbot
Date:   Sat Mar 18 13:42:40 2017 -0700

    pull requests: Check PR commits reference when issue is referenced.

    Fixes #51.

To learn how to write a great commit message, please refer to our guide.

@theofficialvedantjoshi theofficialvedantjoshi force-pushed the update-google-calendar-authentication branch from 3626384 to 50a9579 Compare February 6, 2025 17:32
@theofficialvedantjoshi
Copy link
Author

theofficialvedantjoshi commented Feb 6, 2025

Added the following details to the reminder messages being sent:

  • time interval
  • status
  • location
  • hangoutLink
  • organizer.displayName (if organizer.self is not set, i.e., the organizer is not the owner of the calendar)
  • htmlLink

Also, events without a title are shown as "(No title)".

image
image

@theofficialvedantjoshi
Copy link
Author

Will be working on features related to reminder.overrides next.

@theofficialvedantjoshi
Copy link
Author

Fixed reminder logic to respect Google Calendar's reminders.overrides

  • Implemented event-specific reminder times by leveraging reminders.overrides.minutes when available.
  • Added support for reminders.useDefault, pulling default calendar reminders if event overrides are absent.
  • Introduced an --override option to force a uniform reminder time for all events.
  • Modified event handling to prevent reminders for past events.
  • Optimized event processing by sorting and iterating based on reminder time.

@theofficialvedantjoshi theofficialvedantjoshi changed the title zulip-integrations: Update google oauth for google calendar integration. zulip-integrations: Update google oauth and reminders for google calendar integration. Feb 7, 2025
@theofficialvedantjoshi
Copy link
Author

@Niloth-p @alya could you please review the updated code?

@alya alya added the maintainer review PR is ready for review by Zulip maintainers. label Feb 13, 2025
@alya
Copy link
Contributor

alya commented Feb 13, 2025

@Niloth-p Tagging for your review, but I'm not following the details here, so please feel free to change the status on this PR if needed.

@theofficialvedantjoshi
Copy link
Author

@Niloth-p any updates?

Copy link
Contributor

@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.

I've successfully absorbed the first 3 commits into my branch. Thanks for the work!

I haven't had the time to implement the last commit. So, I'm leaving some review comments regarding the implementation idea I have in mind.
I haven't checked for edge cases, so you'll have to build upon the suggested idea, and check for them.

@@ -40,13 +40,14 @@ class Event(TypedDict):
description: str
organizer: str
hangout_link: str
reminder: int
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's add a comment explaining this field here.

Choose a reason for hiding this comment

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

Will do.

# Unique keys for events we've already sent, so we don't remind twice.
sent: Set[Tuple[int, datetime.datetime]] = set()
# Unique keys for reminders we've already sent, so we don't remind twice.
sent: Set[Tuple[int, datetime.datetime, int]] = set()
Copy link
Contributor

Choose a reason for hiding this comment

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

How about instead of adding a separate reminder field, we actually switch the start_time field in the tuple to instead use the reminder_time?

Choose a reason for hiding this comment

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

This would be better will make this change

@@ -73,12 +74,11 @@ google-calendar --calendar [email protected]


parser.add_argument(
"--interval",
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's not change this.
This is not meant to be an override, this is the default value for the interval we're going to be using.
This shouldn't be a required argument, but an optional one with the default value as 30 mins.

Choose a reason for hiding this comment

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

Can you explain this argument properly? What is it supposed to signify?

timeMin=now,
maxResults=5,
timeMin=datetime.datetime.now(pytz.utc).isoformat(),
timeMax=datetime.datetime.now(pytz.utc).isoformat().split("T")[0] + "T23:59:59Z",
Copy link
Contributor

Choose a reason for hiding this comment

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

maxResults=5 is definitely restricting and needs to be updated for the new algorithm, but I'm not sure we want to pick only events for the same day.
For example, what if the user wants to send reminders like "7 days to go!", "3 days to go", etc. We should be able to support those too.
So, I'm not yet sure what we can do here, to avoid fetching too many events every single call, especially since we do events.clear() each time.

There are other options like not clearing our cache, and getting notifications from Google, on the calendar being updated instead. But again, I haven't looked into the feasibility of it, and whether that's necessary or optimal. And that would warrant more commits.

Choose a reason for hiding this comment

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

We can deal with this later I guess. Will try to think of a feasible solution.

@@ -162,6 +161,9 @@ def populate_events() -> Optional[None]:
# of the tzinfo base class.
start = calendar_timezone.localize(start_naive)
end = calendar_timezone.localize(end_naive)
now = datetime.datetime.now(tz=start.tzinfo)
if start < now:
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure why this is required.
We're using timeMin = now, so why would start be < now?

Choose a reason for hiding this comment

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

Fetching events with timeMin = now also fetches events that are ongoing for some reason and we want to ignore those events since they have already started. Hence a small check to ignore these events.

Example:

  • This event is ongoing:

    image

  • When I print the feed of events received (print(feed)) I see this event popping up:

    image

@@ -176,7 +178,25 @@ def populate_events() -> Optional[None]:
else event["organizer"].get("displayName", event["organizer"]["email"])
)
hangout_link = event.get("hangoutLink", "")
events.append(
reminders = event["reminders"]
# If the user has specified an override, we use that for all events.
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's actually do this instead.

Let's ignore the calendar's default reminders value.
The only value we'll use is event["reminders"]["overrides"]["minutes"].

We'll use 30 mins as the default interval.
If the user wants something else, they can pass it in with --interval.

And for particular events with reminders set for x intervals, we override using the interval.

So, something like the below.

Suggested change
# If the user has specified an override, we use that for all events.
if event.get("reminders", {}).get("overrides"):
for override in event["reminders"]["overrides"]:
reminder_interval = override["minutes"]
event["reminder"] = event["start"] - datetime.timedelta(minutes=reminder_interval)
# Call events.append() for each override

Note that I'm suggesting setting event["reminder"] not to event["reminders"]["overrides"]["minutes"], but actually the time it should be called, as a replacement for event["start"].

And I know I had previously mentioned not sending notifications if it's turned off for particular events, after taking a look at the API. But, event["reminders"]["useDefault"] seems to return False by default, and I haven't yet figured out how to set it to True through the Google Calendar UI. So, unless there's an easy way to do that, and its commonly used (and you can figure it out), I think we can drop that, at least for now.

# repeating events.
key = (event["id"], event["start"])
# Sort events by the time of the reminder.
events.sort(
Copy link
Contributor

Choose a reason for hiding this comment

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

Switching to using "reminder" instead of "start" can now simplify this to:

Suggested change
events.sort(
events.sort(key=lambda event: (event["reminder"]))

)
# Iterate through the events and send reminders for those whose reminder time has come or passed and remove them from the list.
# The instant a reminder's time is greater than the current time, we stop sending reminders and break out of the loop.
while len(events):
Copy link
Contributor

@Niloth-p Niloth-p Feb 20, 2025

Choose a reason for hiding this comment

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

Switching to using "reminder" instead of "start" can now simplify this to using the previous logic itself, so this section of changes wouldn't be needed.

Just "start" -> "reminder".

Suggested change
while len(events):
dt = event["reminder"] - now
if dt.days == 0 and dt.seconds > 0:
# The unique key includes the reminder time due to repeating events, and reminder overrides.
key = (event["id"], event["reminder"])

Choose a reason for hiding this comment

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

Yes makes sense will do.

@Niloth-p Niloth-p mentioned this pull request Feb 20, 2025
12 tasks
@Niloth-p
Copy link
Contributor

Hey, thanks for adding that RefreshError commit addressing that, but that's actually not the fix for that issue. It's not the client-secret key that needs the refresh. I've added the fix for this in #856.
You could actually pull that #856 branch using ./tools/fetch-pull-request <number>, and add your commits on top of it, and push to your own fork, and ping me, I'll update #856 with your commits when I can. That would be easier for both of us, because otherwise I would need to rebase your changes onto the PR, and there may be lots of conflicts. And it might take a while before #856 gets merged.

Also, if you're interested, could you help me add automated tests for the google calendar integration?
We should at least have tests covering the basic behavior - the tokens file being generated given a valid client secret file, and the messages being posted given event data.

@theofficialvedantjoshi
Copy link
Author

Alright, I am interested in adding automated tests Will do these once the above issues are addressed.
Just need to clarify somethings. Will I push to the branch on your fork or how does it work exactly?

@theofficialvedantjoshi
Copy link
Author

Reviewing my fix for google.auth.exceptions.RefreshError I understand the client_secret file doesn't need to be recreated instead the google-credentials file does. Is a valid fix just asking the user to remove the existing google-credentials and rerun the get-google-credentials ?

@Niloth-p
Copy link
Contributor

Both answers are already mentioned. I've added more details now.

  1. "Will I push to the branch on your fork or how does it work exactly?"

push to your own fork, and ping me

Pull the branch from #856 using the command above.
Create a new branch on top of it, and push that to your fork, and ping me here or on CZO with the link to your branch in your fork.

  1. "Is a valid fix just asking the user to remove the existing google-credentials and rerun the get-google-credentials ?"

I've added the fix for this in #856.

It's part of this commit.

  1. Also, the tests would actually help both our changes get merged faster, because they'd require a less thorough review. Just letting you know about their relative importance, over adding more commits with fixes.
  2. Feel free to review Update Google Calendar integration #856 too. It would definitely help to have another pair of eyes take a look, and check for any overlooked edge cases. But just make sure that you've gone through all the commits, commit messages, and the PR description before you raise any concerns, to ensure that they have not already been addressed somewhere.

@theofficialvedantjoshi
Copy link
Author

I have added the reminder logic to #856 # in my branch keeping in mind the above resolutions.

To clarify

But, event["reminders"]["useDefault"] seems to return False by default, and I haven't yet figured out how to set it to True through the Google Calendar UI.

There is a way to change the default by going into calendar settings and changing the Event notifications. In my original commit I had changed this default and tested this scenario.

image

Please review my changes and let me know what else needs to added. Also I still do not understand the point of --interval if we fetch reminders directly from the calendar why would we ever use --interval.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
maintainer review PR is ready for review by Zulip maintainers. size: XL
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Update the Google Calendar integration
4 participants