Skip to content

Commit

Permalink
Demo - Room reservation (#47)
Browse files Browse the repository at this point in the history
Co-authored-by: Daniel Abraham <[email protected]>
  • Loading branch information
haimzlato and daabr authored Aug 28, 2024
1 parent 9c33d96 commit 4f101ec
Show file tree
Hide file tree
Showing 7 changed files with 274 additions and 17 deletions.
35 changes: 18 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,24 @@ projects for:
- Composable templates for interoperability between common services
- Demonstrations of system capabilities and features

| Name | Description | Integrations |
| :------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------- | :--------------------------------- |
| 🐍 [AWS Health to Slack](./aws_health_to_slack/) | Announce cloud health events based on a resource ownership mapping | AWS (Health), Google Sheets, Slack |
| 🐍 [Break-glass](./break_glass/) | Manage break-glass requests and approvals for temporary elevated permissions | Slack &rarr; AWS (IAM), Jira |
| 🐍 [Categorize emails](./categorize_emails/) | Categorize new emails and notify the appropriate channels based on the content | Gmail &rarr; ChatGPT &rarr; Slack |
| 🐍 [Confluence to Slack](./confluence_to_slack/) | Notify when a new page with a specific label is created | Confluence &rarr; Slack |
| 🐍 [Create Jira issue via webhook](./create_jira_issue/) | Create Jira issues with HTTP GET/POST requests | HTTP &rarr; Jira |
| 🐍 [Data pipeline](./data_pipeline/) | Process and store data from new S3 files in a database | AWS (SNS, S3) &rarr; SQLite |
|[GitHub Copilot seats](./github_copilot/) | Automate daily GitHub Copilot user pruning and report changes | GitHub &harr; Slack |
| 🐍 [Google Forms to Jira](./google_forms_to_jira/) | Poll a form for responses and create an issue for each one | Google Forms &rarr; Jira |
| 🐍 [Jira assignee from schedule](./jira_google_calendar/assignee_from_schedule/) | Assign new Jira issues to the current on-caller based on a schedule in a shared calendar | Jira &harr; Google Calendar |
| 🐍 [Jira deadline to event](./jira_google_calendar/deadline_to_event/) | Create/update calendar events based on the deadlines of Jira issues | Jira &harr; Google Calendar |
| 🐍 [Quickstart](./quickstart/) | Basic workflow for tutorials | HTTP |
|[Pull Request Review Reminder (Purrr)](./purrr/) | Streamline code reviews and cut down turnaround time to merge pull requests | GitHub &harr; Slack |
|[ReviewKitteh](./reviewkitteh/) | Monitor pull requests, and meow at random people | GitHub, Google Sheets, Slack |
| 🐍 [Task chain](./task_chain/) | Run a sequence of tasks with fault tolerance | Slack |
| 🐍 [Slack Support](./slack_support/) | Categorize slack support requests using AI and make sure appropiate people handle them | Slack, Google Sheets, Gemini |
| Name | Description | Integrations |
| :------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------- | :------------------------------------------ |
| 🐍 [AWS Health to Slack](./aws_health_to_slack/) | Announce cloud health events based on a resource ownership mapping | AWS (Health), Google Sheets, Slack |
| 🐍 [Break-glass](./break_glass/) | Manage break-glass requests and approvals for temporary elevated permissions | Slack &rarr; AWS (IAM), Jira |
| 🐍 [Categorize emails](./categorize_emails/) | Categorize new emails and notify the appropriate channels based on the content | Gmail &rarr; ChatGPT &rarr; Slack |
| 🐍 [Confluence to Slack](./confluence_to_slack/) | Notify when a new page with a specific label is created | Confluence &rarr; Slack |
| 🐍 [Create Jira issue via webhook](./create_jira_issue/) | Create Jira issues with HTTP GET/POST requests | HTTP &rarr; Jira |
| 🐍 [Data pipeline](./data_pipeline/) | Process and store data from new S3 files in a database | AWS (SNS, S3) &rarr; SQLite |
|[GitHub Copilot seats](./github_copilot/) | Automate daily GitHub Copilot user pruning and report changes | GitHub &harr; Slack |
| 🐍 [Google Forms to Jira](./google_forms_to_jira/) | Poll a form for responses and create an issue for each one | Google Forms &rarr; Jira |
| 🐍 [Jira assignee from schedule](./jira_google_calendar/assignee_from_schedule/) | Assign new Jira issues to the current on-caller based on a schedule in a shared calendar | Jira &harr; Google Calendar |
| 🐍 [Jira deadline to event](./jira_google_calendar/deadline_to_event/) | Create/update calendar events based on the deadlines of Jira issues | Jira &harr; Google Calendar |
|[Pull Request Review Reminder (Purrr)](./purrr/) | Streamline code reviews and cut down turnaround time to merge pull requests | GitHub &harr; Slack |
| 🐍 [Quickstart](./quickstart/) | Basic workflow for tutorials | HTTP |
|[ReviewKitteh](./reviewkitteh/) | Monitor pull requests, and meow at random people | GitHub, Google Sheets, Slack |
| 🐍 [Room reservation](./room_reservation/) | Manage via Slack ad-hoc room reservations in Google Calendar | Slack &harr; Google Calendar, Google Sheets |
| 🐍 [Slack support](./slack_support/) | Categorize Slack support requests using AI, and route them to the appropiate people | Slack &harr; Gemini, Google Sheets |
| 🐍 [Task chain](./task_chain/) | Run a sequence of tasks with fault tolerance | Slack |

> [!NOTE]
> 🐍 = Python implementation, ⭐ = Starlark implementation.
43 changes: 43 additions & 0 deletions room_reservation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Room Reservarion

In our organization, we manage meeting rooms as resources in Google Calendar:

- Each meeting room is represented by an email account
- To reserve a room for a meeting, users can add it to the invite

While users can reserve rooms directly from the calendar, we wanted to add a
Slack interface to make it even quicker to reserve a room for an immediate
meeting within the next half hour.

We configured three slash commands in Slack:

- `/availablerooms` - list all the available rooms
- `/roomstatus <room>` - check the status of a specific room
- `/reserveroom <room> <title>` - reserve a specific room

> [!TIP]
> You can extend this project to add participants, set the time, etc.
## Configuration

Each meeting room is represented by an email account.

The list of available meeting rooms is stored in a Google Sheet as a single
column of room email addresses:

| | A |
| :-: | :-----------------: |
| 1 | `[email protected]` |
| 2 | `[email protected]` |
| 3 | `[email protected]` |

Before deploying this project, set the `GOOGLE_SHEET_ID` variable in the
[autokitteh.yaml](./autokitteh.yaml) manifest file, to point to your Google
Sheet.

> [!TIP]
> You can extend this project to add another column for user-friendly aliases.
## AutoKitteh Integrations

Google Calendar, Google Sheets, Slack
40 changes: 40 additions & 0 deletions room_reservation/autokitteh.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# This YAML file is a declarative manifest that describes the setup
# of an AutoKitteh project that manages via Slack ad-hoc room
# reservations in Google Calendar.
#
# After creating this AutoKitteh project by applying this file,
# initialize its Google Calendar, Google Sheets, and Slack connections.
#
# Before deploying this AutoKitteh project, set the GOOGLE_SHEET_ID
# variable to point to a Google Sheet, as described in the README.md file.

version: v1

project:
name: room_reservation
vars:
- name: GOOGLE_SHEET_ID
value:
connections:
- name: calendar_conn
integration: googlecalendar
- name: sheets_conn
integration: googlesheets
- name: slack_conn
integration: slack
triggers:
- name: slack_slash_command_available_rooms
connection: slack_conn
event_type: slash_command
filter: data.command == "/availablerooms"
call: available_rooms.py:on_slack_slash_command
- name: slack_slash_command_room_status
connection: slack_conn
event_type: slash_command
filter: data.command == "/roomstatus"
call: room_status.py:on_slack_slash_command
- name: slack_slash_command_reserve_room
connection: slack_conn
event_type: slash_command
filter: data.command == "/reserveroom"
call: reserve_room.py:on_slack_slash_command
51 changes: 51 additions & 0 deletions room_reservation/available_rooms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""List all the available rooms for the next half hour."""

from datetime import UTC, datetime, timedelta

from autokitteh.google import google_calendar_client
from autokitteh.slack import slack_client
from googleapiclient.errors import HttpError

import google_sheets


def on_slack_slash_command(event):
"""Entry point for the "/availablerooms" Slack slash command."""
slack = slack_client("slack_conn")
channel_id = event.data.user_id # event.data.channel_id

now = datetime.now(UTC)
in_30_minutes = now + timedelta(minutes=30)
gcal = google_calendar_client("calendar_conn").events()

# Iterate over the list of rooms, notify the user about
# each room which is available in the next half hour.
available = False
for room in sorted(google_sheets.get_room_list()):
print(f"Checking upcoming events in: {room}")
try:
events = gcal.list(
calendarId=room,
timeMin=now.isoformat(),
timeMax=in_30_minutes.isoformat(),
singleEvents=True,
orderBy="startTime",
).execute()

events = events.get("items", [])
# Ignore non-blocking events where the room is marked as "free".
events = [e for e in events if e.get("transparency", "") != "transparent"]

if not events:
msg = f"The room `{room}` is available for the next half hour"
slack.chat_postMessage(channel=channel_id, text=msg)
available = True

except HttpError as e:
err = f"Error for the room `{room}`: '{e.reason}'"
slack.chat_postMessage(channel=channel_id, text=err)
print(f"Error when listing events for room `{room}`: {e}")

if not available:
msg = "No available rooms found for the next half hour"
slack.chat_postMessage(channel=channel_id, text=msg)
9 changes: 9 additions & 0 deletions room_reservation/google_sheets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import os

from autokitteh.google import google_sheets_client


def get_room_list():
sheet = google_sheets_client("sheets_conn").spreadsheets().values()
rows = sheet.get(spreadsheetId=os.getenv("GOOGLE_SHEET_ID"), range="A:A").execute()
return [cell[0] for cell in rows.get("values", []) if cell]
60 changes: 60 additions & 0 deletions room_reservation/reserve_room.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""Reserve a specific room for the next half hour."""

from datetime import UTC, datetime, timedelta

import autokitteh
from autokitteh.google import google_calendar_client
from autokitteh.slack import slack_client
from googleapiclient.errors import HttpError

import google_sheets


def on_slack_slash_command(event):
"""Entry point for the "/reserveroom <room> <title>" Slack slash command."""
slack = slack_client("slack_conn")
channel_id = event.data.user_id # event.data.channel_id

cmd_text = event.data.text.split(maxsplit=1)
if len(cmd_text) < 2:
err = "Error: please use the following format: `/reserveroom <room> <title>`"
slack.chat_postMessage(channel=channel_id, text=err)
return

room, title = cmd_text
if room not in google_sheets.get_room_list():
err = f"Error: `{room}` not found in the list of rooms"
slack.chat_postMessage(channel=channel_id, text=err)
return

user = slack.users_profile_get(user=event.data.user_id).get("profile")

now = datetime.now(UTC)
in_5_minutes = now + timedelta(minutes=5)
in_30_minutes = now + timedelta(minutes=30)

event = {
"summary": title,
"description": f"Reserved via Slack by {user['real_name']}",
"start": {"dateTime": in_5_minutes.isoformat()},
"end": {"dateTime": in_30_minutes.isoformat()},
"reminders": {"useDefault": False},
"attendees": [
{"email": user["email"]},
],
}

result = _create_calendar_event(room, event)
slack.chat_postMessage(channel=channel_id, text=result)


# It's better to mark this as a single activity, to minimize the creation of
# multiple overly-granular activities during this Google Calendar API call.
@autokitteh.activity
def _create_calendar_event(room, event):
try:
gcal = google_calendar_client("calendar_conn").events()
gcal.insert(calendarId=room, body=event).execute()
return f"Scheduled a meeting now in the room `{room}`"
except HttpError as e:
return f"Error: failed to schedule a meeting ('{e.reason}')"
53 changes: 53 additions & 0 deletions room_reservation/room_status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""Check the status of a specific room for the next hour."""

from datetime import UTC, datetime, timedelta

import autokitteh
from autokitteh.google import google_calendar_client
from autokitteh.slack import slack_client
from googleapiclient.errors import HttpError

import google_sheets


def on_slack_slash_command(event):
"""Entry point for the "/roomstatus <room>" Slack slash command."""
slack = slack_client("slack_conn")
channel_id = event.data.user_id # event.data.channel_id

room = event.data.text
if room not in google_sheets.get_room_list():
err = f"Error: `{room}` not found in the list of rooms"
slack.chat_postMessage(channel=channel_id, text=err)

gcal = google_calendar_client("calendar_conn").events()
now = datetime.now(UTC)
in_1_hour = now + timedelta(hours=1)

msg = f"Events in the room `{room}`:"
try:
events = gcal.list(
calendarId=room,
timeMin=now.isoformat(),
timeMax=in_1_hour.isoformat(),
singleEvents=True,
orderBy="startTime",
).execute()

events = events.get("items", [])
# Ignore non-blocking events where the room is marked as "free".
events = [e for e in events if e.get("transparency", "") != "transparent"]

if not events:
msg += " none found for the next half hour"

for event in events:
event = autokitteh.AttrDict(event)
start = event.start.get("dateTime") or event.start.get("date")
msg += f"\n{start} - {event.summary}"

except HttpError as e:
msg += f" error '{e.reason}'"
print(f"Error when listing events for room `{room}`: {e}")

slack.chat_postMessage(channel=channel_id, text=msg)

0 comments on commit 4f101ec

Please sign in to comment.