# What this PR does The following is deployed under a feature flag. **How it works** 1. The user clicks on the "Connect using your Google account" button in the user profile settings modal 2. The UI makes a call to `GET /api/internal/v1/login/google-oauth2`. The backend has now been configured to add `apps.social_auth.backends.GoogleOAuth2` as a "`social_auth` backend". 3. The backend will respond w/ a URL which points to the Google OAuth2 consent screen. The frontend then proceeds by sending the user to this page. This URL includes the following query parameters (amongst others): - `redirect_uri` - this will send the user back to `/api/internal/v1/complete/google-oauth2` (ie. make another API call to the OnCall backend to finalize the Google OAuth2 flow) - `state` - this represents an `apps.auth_token.models.GoogleOAuth2Token` token. This allows us to identify the OnCall user once they've linked their Google account. 4. Once redirected back to `/api/internal/v1/complete/google-oauth2`, this will complete the OAuth2 flow. At this point, the backend has access to several pieces of information about the Google user, including their `access_token` and `refresh_token`. We persist these (encrypted) for future use to fetch the user's out-of-office calendar events 5. The response from the API call in 4 above ☝️ is HTTP 302 (redirect) to `/a/grafana-oncall-app/users/me` (ie. open the user profile settings modal). At this point the user will see that their account has been connected and they can further configure the settings  ## Which issue(s) this PR closes Closes https://github.com/grafana/oncall-private/issues/2584 ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - will be done in https://github.com/grafana/oncall-private/issues/2591 - [x] Added the relevant release notes label (see labels prefixed w/ `release:`). These labels dictate how your PR will show up in the autogenerated release notes. - will be done in https://github.com/grafana/oncall-private/issues/2591 --------- Co-authored-by: Dominik <dominik.broj@grafana.com> Co-authored-by: Maxim Mordasov <maxim.mordasov@grafana.com>
76 lines
2.7 KiB
Python
76 lines
2.7 KiB
Python
import datetime
|
|
import logging
|
|
import typing
|
|
|
|
from django.conf import settings
|
|
from google.oauth2.credentials import Credentials
|
|
from googleapiclient.discovery import build
|
|
|
|
from apps.google.types import GoogleCalendarEvent
|
|
|
|
logger = logging.getLogger(__name__)
|
|
logger.setLevel(logging.DEBUG)
|
|
|
|
|
|
class GoogleCalendarAPIClient:
|
|
MAX_NUMBER_OF_CALENDAR_EVENTS_TO_FETCH = 250
|
|
"""
|
|
By default the value is 250 events. The page size can never be larger than 2500 events
|
|
"""
|
|
|
|
CALENDAR_ID = "primary"
|
|
"""
|
|
for right now we only consider the user's primary calendar. If in the future we
|
|
want to allow the user to specify a different calendar, we'd need to [retrieve all their calendars](https://developers.google.com/calendar/v3/reference/calendarList/list)
|
|
, display this list to them + perist their choice
|
|
|
|
See `calendarId` under the "Parameters" section [here](https://developers.google.com/calendar/api/v3/reference/events/list)
|
|
"""
|
|
|
|
def __init__(self, access_token: str, refresh_token: str):
|
|
"""
|
|
https://developers.google.com/calendar/api/quickstart/python
|
|
https://google-auth.readthedocs.io/en/stable/reference/google.oauth2.credentials.html
|
|
"""
|
|
credentials = Credentials(
|
|
token=access_token,
|
|
refresh_token=refresh_token,
|
|
token_uri="https://www.googleapis.com/oauth2/v3/token",
|
|
client_id=settings.SOCIAL_AUTH_GOOGLE_OAUTH2_KEY,
|
|
client_secret=settings.SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET,
|
|
)
|
|
|
|
self.service = build("calendar", "v3", credentials=credentials)
|
|
|
|
def fetch_out_of_office_events(self) -> typing.List[GoogleCalendarEvent]:
|
|
"""
|
|
https://developers.google.com/calendar/api/v3/reference/events/list
|
|
"""
|
|
|
|
def _format_datetime_arg(dt: datetime.datetime) -> str:
|
|
"""
|
|
https://stackoverflow.com/a/17159470/3902555
|
|
"""
|
|
return dt.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
|
|
now = _format_datetime_arg(datetime.datetime.now(datetime.UTC))
|
|
|
|
logger.info(
|
|
f"GoogleCalendarAPIClient - Getting the upcoming {self.MAX_NUMBER_OF_CALENDAR_EVENTS_TO_FETCH} "
|
|
"out of office events"
|
|
)
|
|
|
|
events_result = (
|
|
self.service.events()
|
|
.list(
|
|
calendarId=self.CALENDAR_ID,
|
|
timeMin=now,
|
|
# timeMax= TODO: should we only fetch out of office events for next X amount of time?
|
|
maxResults=self.MAX_NUMBER_OF_CALENDAR_EVENTS_TO_FETCH,
|
|
singleEvents=True,
|
|
orderBy="startTime",
|
|
eventTypes="outOfOffice",
|
|
)
|
|
.execute()
|
|
)
|
|
return events_result.get("items", [])
|