Skip to content

Commit

Permalink
Feat/google meet creation (#715)
Browse files Browse the repository at this point in the history
* feat: setup a Google Meet class to handle Hangouts

* feat: update link creation with new Google Meet
  • Loading branch information
gcharest authored Jan 15, 2025
1 parent e7b6ea1 commit 7b66c9e
Show file tree
Hide file tree
Showing 4 changed files with 209 additions and 50 deletions.
47 changes: 47 additions & 0 deletions app/integrations/google_next/meet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from googleapiclient.discovery import Resource # type: ignore
from integrations.google_next.service import (
execute_google_api_call,
handle_google_api_errors,
get_google_service,
GOOGLE_DELEGATED_ADMIN_EMAIL,
)


class GoogleMeet:
"""
A class to simplify the use of various Google Meet API operations across modules.
This class provides methods to interact with the Google Workspace Meet API, including creating new meetings, updating existing meetings, and retrieving meeting details, and includes error handling for Google API errors.
While this class aims to simplify the usage of the Google Meet API, it is always possible
to use the Google API Python client directly as per the official documentation:
(https://googleapis.github.io/google-api-python-client/docs/)
Attributes:
scopes (list): The list of scopes to request.
delegated_email (str): The email address of the user to impersonate.
service (Resource): Optional - An authenticated Google service resource. If provided, the service will be used instead of creating a new one.
"""

def __init__(
self, scopes=None, delegated_email=None, service: Resource | None = None
):
if not scopes and not service:
raise ValueError("Either scopes or a service must be provided.")
if not delegated_email and not service:
delegated_email = GOOGLE_DELEGATED_ADMIN_EMAIL
self.scopes = scopes
self.delegated_email = delegated_email
self.service = service if service else self._get_google_service()

def _get_google_service(self) -> Resource:
"""Get authenticated directory service for Google Workspace."""
return get_google_service("meet", "v2", self.scopes, self.delegated_email)

@handle_google_api_errors
def create_space(self):
"""Creates a new and empty space in Google Meet."""
config = {"accessType": "TRUSTED", "entryPointAccess": "ALL"}
return execute_google_api_call(
self.service, "spaces", "create", body={"config": config}
)
15 changes: 9 additions & 6 deletions app/modules/incident/incident.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from integrations import opsgenie
from integrations.slack import users as slack_users
from integrations.sentinel import log_to_sentinel
from integrations.google_next.meet import GoogleMeet

from modules.incident import (
incident_alert,
Expand Down Expand Up @@ -201,6 +202,8 @@ def handle_change_locale_button(ack, client, body):
def submit(ack, view, say, body, client: WebClient, logger):
ack()

gmeet_scopes = ["https://www.googleapis.com/auth/meetings.space.created"]
gmeet = GoogleMeet(scopes=gmeet_scopes)
errors = {}

name = view["state"]["values"]["name"]["name"]["value"]
Expand Down Expand Up @@ -276,15 +279,15 @@ def submit(ack, view, say, body, client: WebClient, logger):
client.conversations_invite(channel=channel_id, users=user_id)

# Add meeting link
meet_link = f"https://g.co/meet/incident-{slug}"
# Max character length for Google Meet nickname is 60, 78 with constant URI
if len(meet_link) > 78:
meet_link = meet_link[:78]
meet_link = gmeet.create_space()
client.bookmarks_add(
channel_id=channel_id, title="Meet link", type="link", link=meet_link
channel_id=channel_id,
title="Meet link",
type="link",
link=meet_link["meetingUri"],
)

text = f"A hangout has been created at: {meet_link}"
text = f"A hangout has been created at: {meet_link['meetingUri']}"
say(text=text, channel=channel_id)

# Create incident document
Expand Down
75 changes: 75 additions & 0 deletions app/tests/integrations/google_next/test_meet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
""""Unit tests for the Google Meet API."""

from unittest.mock import MagicMock, patch

import pytest
from integrations.google_next.meet import GoogleMeet


@pytest.fixture(scope="class")
@patch("integrations.google_next.meet.get_google_service")
def google_meet(mock_get_google_service) -> GoogleMeet:
scopes = ["https://www.googleapis.com/auth/meetings.space.created"]
delegated_email = "[email protected]"
mock_get_google_service.return_value = MagicMock()
return GoogleMeet(scopes, delegated_email)


class TestGoogleMeet:
@pytest.fixture(autouse=True)
def setup(self, google_meet: GoogleMeet):
self.google_meet = google_meet

def test_init_without_scopes_and_service(self):
"""Test initialization without scopes and service raises ValueError."""
with pytest.raises(
ValueError, match="Either scopes or a service must be provided."
):
GoogleMeet(delegated_email="[email protected]")

@patch(
"integrations.google_next.meet.GOOGLE_DELEGATED_ADMIN_EMAIL",
new="[email protected]",
)
@patch("integrations.google_next.meet.get_google_service")
def test_init_without_delegated_email_and_service(self, mock_get_google_service):
"""Test initialization without delegated email and service uses default email."""
mock_get_google_service.return_value = MagicMock()
google_meet = GoogleMeet(
scopes=["https://www.googleapis.com/auth/meetings.space.created"]
)
assert google_meet.scopes == [
"https://www.googleapis.com/auth/meetings.space.created"
]
assert google_meet.delegated_email == "[email protected]"

@patch("integrations.google_next.meet.get_google_service")
def test_get_google_service(self, mock_get_google_service):
"""Test get_docs_service returns a service."""
mock_get_google_service.return_value = MagicMock()
service = self.google_meet._get_google_service()
assert service is not None
mock_get_google_service.assert_called_once_with(
"meet",
"v2",
self.google_meet.scopes,
self.google_meet.delegated_email,
)

@patch("integrations.google_next.meet.execute_google_api_call")
def test_create_space(self, mock_execute_google_api_call):
"""Test create_space returns a response."""
mock_execute_google_api_call.return_value = {
"name": "spaces/asdfasdf",
"meetingUri": "https://meet.google.com/aaa-bbbb-ccc",
"meetingCode": "aaa-bbbb-ccc",
"config": {"accessType": "TRUSTED", "entryPointAccess": "ALL"},
}
response = self.google_meet.create_space()
assert response is not None
mock_execute_google_api_call.assert_called_once_with(
self.google_meet.service,
"spaces",
"create",
body={"config": {"accessType": "TRUSTED", "entryPointAccess": "ALL"}},
)
Loading

0 comments on commit 7b66c9e

Please sign in to comment.