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

Feat/google groups members report #696

Merged
merged 17 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions app/integrations/google_workspace/google_drive.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,14 +198,26 @@ def create_file(name, folder, file_type):
"create",
body={"name": name, "parents": [folder], "mimeType": mime_type_value},
supportsAllDrives=True,
fields="id",
fields="id, name",
)

return result["id"]
return result


@handle_google_api_errors
def get_file_by_id(id):
return execute_google_api_call(
"drive",
"v3",
"files",
"get",
fileId=id,
supportsAllDrives=True,
)


@handle_google_api_errors
def get_file_by_name(name, folder_id=None):
def find_files_by_name(name, folder_id=None):
"""Get a file by name in a specific Google Drive folder.

This function requires the caller to have the necessary permissions to access the file in Google Workspace.
Expand Down
18 changes: 14 additions & 4 deletions app/integrations/google_workspace/google_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ def handle_google_api_errors(func: Callable[..., Any]) -> Callable[..., Any]:
def wrapper(*args: Any, **kwargs: Any) -> Any:
non_critical_errors = {
"get_user": ["timed out"],
"get_sheet": ["Unable to parse range"],
}
argument_string = ", ".join(
[str(arg) for arg in args] + [f"{k}={v}" for k, v in kwargs.items()]
Expand All @@ -103,10 +104,19 @@ def wrapper(*args: Any, **kwargs: Any) -> Any:
)
return result
except HttpError as e:
logging.error(
f"An HTTP error occurred in function '{func.__module__}:{func.__name__}': {e}"
)
raise e
message = str(e)
func_name = func.__name__
if func_name in non_critical_errors and any(
error in message for error in non_critical_errors[func_name]
):
logging.warning(
f"A non critical error occurred in function '{func.__module__}:{func.__name__}{argument_string}': {e}"
)
else:
logging.error(
f"An HTTP error occurred in function '{func.__module__}:{func.__name__}': {e}"
)
raise e
except ValueError as e:
logging.error(
f"A ValueError occurred in function '{func.__module__}:{func.__name__}': {e}"
Expand Down
44 changes: 44 additions & 0 deletions app/integrations/google_workspace/sheets.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,50 @@ def get_values(spreadsheetId: str, range: str | None = None, fields=None):
)


@handle_google_api_errors
def get_sheet(spreadsheetId: str, ranges: str):
"""Gets a Google Sheet.

Args:
spreadsheetId (str): The id of the Google Sheet.
sheetId (int): The id of the sheet.

Returns:
dict: The response from the Google Sheets API.
"""
return execute_google_api_call(
"sheets",
"v4",
"spreadsheets",
"get",
spreadsheetId=spreadsheetId,
ranges=ranges,
includeGridData=False,
)


@handle_google_api_errors
def batch_update(spreadsheetId: str, body: dict):
"""Updates a Google Sheet.

Args:
spreadsheetId (str): The id of the Google Sheet.
body (dict): The request body.

Returns:
dict: The response from the Google Sheets API.
"""
return execute_google_api_call(
"sheets",
"v4",
"spreadsheets",
"batchUpdate",
spreadsheetId=spreadsheetId,
body=body,
)


@handle_google_api_errors
def batch_update_values(
spreadsheetId: str,
range: str,
Expand Down
42 changes: 9 additions & 33 deletions app/modules/dev/google.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

import os

from integrations.google_workspace import gmail
from integrations.slack import users as slack_users
from integrations.google_workspace import (
google_directory,
)

from dotenv import load_dotenv

Expand All @@ -12,43 +13,18 @@
SRE_DRIVE_ID = os.environ.get("SRE_DRIVE_ID")
SRE_INCIDENT_FOLDER = os.environ.get("SRE_INCIDENT_FOLDER")
INCIDENT_TEMPLATE = os.environ.get("INCIDENT_TEMPLATE")
REPORT_GOOGLE_GROUPS_FOLDER = "18IYoyg5AFz3ZsZSSqvP1iaJur1V3Fmvi"
GROUPS_MEMBERSHIPS_FOLDER = "1KPwrP-fWA0VVCrxW22Z4GJLcTb5-Avjf"
gcharest marked this conversation as resolved.
Show resolved Hide resolved


def open_modal(client, body, folders):
if not folders:
return
folder_names = [i["name"] for i in folders]
blocks = [
{"type": "section", "text": {"type": "mrkdwn", "text": f"*{name}*"}}
for name in folder_names
]
view = {
"type": "modal",
"title": {"type": "plain_text", "text": "Folder List"},
"blocks": blocks,
}
client.views_open(trigger_id=body["trigger_id"], view=view)
def get_members(group):
members = google_directory.list_group_members(group)
return members


def google_service_command(ack, client, body, respond, logger):
ack()
post_content = test_content()
user_id = slack_users.get_user_email_from_body(client, body)
create_message = gmail.create_email_message(
"Test ATI message", post_content, user_id, ""
)

response = gmail.create_draft(
message=create_message,
user_id=user_id,
delegated_user_email=user_id,
)

logger.info(response)
if not response:
respond("No response")
else:
respond("Found users")
respond("Nothing to see here.")


def test_content():
Expand Down
2 changes: 1 addition & 1 deletion app/modules/incident/incident_roles.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def manage_roles(client: WebClient, body, ack, respond):
channel_name.startswith("incident-") and len("incident-") :
]
channel_name = channel_name[channel_name.startswith("dev-") and len("dev-") :]
documents = google_drive.get_file_by_name(channel_name)
documents = google_drive.find_files_by_name(channel_name)

if len(documents) == 0:
respond(
Expand Down
3 changes: 3 additions & 0 deletions app/modules/reports/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from . import core

__all__ = ["core"]
42 changes: 42 additions & 0 deletions app/modules/reports/core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from slack_bolt import Ack, Respond
from slack_sdk import WebClient
from logging import Logger

from . import google_groups #


help_text = """
\n `/sre reports help | aide`
\n - show this help text
\n - montre le texte d'aide
\n `/sre reports google-groups`
\n - generate a Google Groups statistics report
\n - générer un rapport statistique sur les groupes Google
\n `/sre reports google-groups-members`
\n - generate a Google Groups Members report
\n - générer un rapport sur les membres des groupes Google"""


def reports_command(
args, ack: Ack, command, logger: Logger, respond: Respond, client: WebClient, body
):
ack()
if len(args) == 0:
respond(help_text)
return
logger.info("SRE reports command received: %s", command["text"])

action, *args = args
logger.info("SRE reports action: %s", action)
logger.info("SRE reports args: %s", args)
match action:
case "help" | "aide":
respond(help_text)
case "google-groups":
google_groups.generate_report(args, respond)
case "google-groups-members":
google_groups.generate_group_members_report(args, respond, logger)
case _:
respond(
"Unknown command. Type `/sre reports help` for a list of commands.\nCommande inconnue. Tapez `/sre reports aide` pour voir une liste de commandes."
)
104 changes: 104 additions & 0 deletions app/modules/reports/google_groups.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import os
import time
from datetime import datetime
from dotenv import load_dotenv
from integrations.google_workspace import (
google_directory,
sheets,
google_drive,
)

load_dotenv()

DELEGATED_USER_EMAIL = os.environ.get("GOOGLE_DELEGATED_ADMIN_EMAIL")
FOLDER_REPORTS_GOOGLE_GROUPS = os.environ.get(
"FOLDER_REPORTS_GOOGLE_GROUPS", "18IYoyg5AFz3ZsZSSqvP1iaJur1V3Fmvi"
)


def generate_report(args, respond):
respond("Generating Google Groups report is not implemented yet.")


def generate_group_members_report(args, respond, logger):
"""Generate a report of Google Groups members."""
exclude_groups = ["AWS-"]
logger.info("Generating Google Groups Members report...")
filename = f"groups_report_{datetime.now().strftime('%Y-%m-%d')}"
logger.info(f"Filename: {filename}")
files = google_drive.find_files_by_name(filename, FOLDER_REPORTS_GOOGLE_GROUPS)

if len(files) == 0:
logger.info("No files found. Creating a new file.")
file = google_drive.create_file(
filename, FOLDER_REPORTS_GOOGLE_GROUPS, "spreadsheet"
)
else:
logger.info("File found. Displaying the first file.")
file = files[0]

logger.info(f"File: {file}")

groups = google_directory.list_groups()
groups = [
group
for group in groups
if not any(exclude in group["name"] for exclude in exclude_groups)
]

if not groups:
respond("No groups found.")
return

groups_with_members = []
for index, group in enumerate(groups):
logger.info(f"Processing group {index + 1}/{len(groups)}: {group['email']}")
members = google_directory.list_group_members(group["email"])
group["members"] = members
groups_with_members.append(group)

for group in groups_with_members:
range = f"{group['name']}"
logger.info(f"Creating sheet '{range}'")
if len(range) > 50:
range = range[:50]

try:
sheet = sheets.get_sheet(file["id"], range)
except Exception:
sheet = None
if sheet:
logger.info(f"Sheet '{range}' already exists")
else:
try:
request = {
"requests": [
{
"addSheet": {
"properties": {
"title": range,
}
}
}
]
}
sheet = sheets.batch_update(file["id"], request)
if sheet:
logger.info(f"Sheet '{range}' created")
except Exception as e:
logger.error(e)
values = [["Group Name", range], ["Email", "Role"]]
range = f"{range}!A1"
for member in group["members"]:
values.append([member["email"], member["role"]])
updated_sheet = sheets.batch_update_values(
file["id"],
range,
values,
)
if updated_sheet:
logger.info(f"Sheet '{group['name']}' updated")

time.sleep(1.1)

respond("Google Groups Members report generated.")
3 changes: 3 additions & 0 deletions app/modules/sre/sre.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from modules.incident import incident_helper
from modules.sre import geolocate_helper, webhook_helper
from modules.dev import core as dev_core
from modules.reports import core as reports
from integrations.slack import commands as slack_commands

help_text = """
Expand Down Expand Up @@ -66,6 +67,8 @@ def sre_command(
dev_core.dev_command(ack, logger, respond, client, body, args)
case "version":
respond(f"SRE Bot version: {os.environ.get('GIT_SHA', 'unknown')}")
case "reports":
reports.reports_command(args, ack, command, logger, respond, client, body)
case _:
respond(
f"Unknown command: `{action}`. Type `/sre help` to see a list of commands. \nCommande inconnue: `{action}`. Entrez `/sre help` pour une liste des commandes valides"
Expand Down
Loading
Loading