-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
18 changed files
with
1,706 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[mypy] | ||
disable_error_code = misc, import-untyped, import-not-found |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
owner: CERN | ||
start_year: 2024 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
graft indico_zoom_rooms/templates | ||
|
||
global-exclude *.pyc __pycache__ .keep |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
# Zoom Rooms Plugin | ||
|
||
## Features | ||
|
||
- Synchronizes the calendars of Zoom Rooms-powered devices with the corresponding room occupancy/Zoom meeting | ||
|
||
## Changelog | ||
|
||
### 3.3 | ||
|
||
First version | ||
|
||
## Details | ||
|
||
[**Zoom Rooms**](https://www.zoom.com/en/products/meeting-rooms/) manages its "bookings" through entries in an Exchange calendar. This plugin synchronizes with Exchange a representation of every Indico time slot which fulfils the following criteria: | ||
|
||
- Is either a Contribution, Session Block or Event; | ||
- Takes place in a room which has a Zoom Rooms-enabled device (i.e. has a `zoom-rooms-calendar-id` attribute) | ||
|
||
![Screenshot of device screen](https://github.com/raw/indico/indico-plugins-cern/master/zoom_rooms/assets/logi_screen.png) | ||
|
||
This plugin relies on an external custom-built REST API endpoint (not provided) which interfaces on our behalf with the Exchange Graph API. | ||
The logic is very similar to livesync or the exchange sync plugin: a queue of operations is kept in a database table and rolled back in case the request fails. | ||
|
||
Objects which are direct tracked by the plugin, through signals, are: | ||
|
||
- Events | ||
- Session Blocks | ||
- Contributions | ||
- VC Rooms (associations) | ||
|
||
The available operations are: | ||
|
||
- CREATE - a new calendar slot should be created, with a given start/end date, location and title | ||
- UPDATE - change the start/end time or title of a calendar slot | ||
- MOVE - change the room ID of a given slot (which practically means deleting it and recreating it in another room's calendar) | ||
- DELETE - delete the slot | ||
|
||
This is a summary of the events handled by the plugin and the actions it takes: | ||
|
||
| Object | Change in `{start, end}_dt` | Change in `location` | Change in `block` | Create | | ||
| -------------- | --------------------------- | --------------------------------------------------------------------------------------------------------- | ----------------------------------- | -------- | | ||
| `Event` | `UPDATE {start_dt, end_dt}` | `CREATE/MOVE/DELETE` depending on the original/target room; trigger change in objects inheriting location | Check change in location for object | `CREATE` | | ||
| `SessionBlock` | `UPDATE {start_dt, end_dt}` | `CREATE/MOVE/DELETE` depending on the original/target room; trigger change in objects inheriting location | Check change in location for object | `CREATE` | | ||
| `Contribution` | `UPDATE {start_dt, end_dt}` | `CREATE/MOVE/DELETE` depending on the original/target room | Check change in location for object | `CREATE` | | ||
|
||
| Object | Change in name | Create | Detach | Attach | Clone | | ||
| ------------------------ | ----------------------------------------------- | -------------------- | -------------------- | -------------------- | -------------------- | | ||
| `VCRoom` | `UPDATE title` in all `VCRoomEventAssociations` | `CREATE link_object` | N/A | N/A | N/A | | ||
| `VCRoomEventAssociation` | N/A | N/A | `DELETE link_object` | `CREATE link_object` | `CREATE link_object` | | ||
|
||
This plugins relies on the `vc_zoom` plugin being available and enabled. |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
# This file is part of the CERN Indico plugins. | ||
# Copyright (C) 2024 CERN | ||
# | ||
# The CERN Indico plugins are free software; you can redistribute | ||
# them and/or modify them under the terms of the MIT License; see | ||
# the LICENSE file for more details. | ||
|
||
pytest_plugins = ['indico', 'indico_vc_zoom.fixtures'] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# This file is part of the CERN Indico plugins. | ||
# Copyright (C) 2024 CERN | ||
# | ||
# The CERN Indico plugins are free software; you can redistribute | ||
# them and/or modify them under the terms of the MIT License; see | ||
# the LICENSE file for more details. | ||
|
||
from indico.core import signals | ||
from indico.util.i18n import make_bound_gettext | ||
|
||
|
||
_ = make_bound_gettext('zoom_rooms') | ||
|
||
|
||
@signals.core.import_tasks.connect | ||
def _import_tasks(sender, **kwargs): | ||
import indico_zoom_rooms.tasks # noqa: F401 |
62 changes: 62 additions & 0 deletions
62
...o_zoom_rooms/migrations/20240807_1044_546d4e9de960_add_structures_for_zoom_rooms_queue.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
# This file is part of the CERN Indico plugins. | ||
# Copyright (C) 2024 CERN | ||
# | ||
# The CERN Indico plugins are free software; you can redistribute | ||
# them and/or modify them under the terms of the MIT License; see | ||
# the LICENSE file for more details. | ||
|
||
"""Add structures for zoom rooms queue | ||
Revision ID: 546d4e9de960 | ||
Revises: | ||
Create Date: 2024-08-07 10:44:37.218268 | ||
""" | ||
|
||
from enum import Enum | ||
|
||
import sqlalchemy as sa | ||
from alembic import op | ||
from sqlalchemy.dialects import postgresql | ||
from sqlalchemy.sql.ddl import CreateSchema, DropSchema | ||
|
||
from indico.core.db.sqlalchemy import PyIntEnum | ||
|
||
|
||
# revision identifiers, used by Alembic. | ||
revision = '546d4e9de960' | ||
down_revision = None | ||
branch_labels = None | ||
depends_on = None | ||
|
||
|
||
class _ZoomRoomsAction(int, Enum): | ||
create = 0 | ||
update = 1 | ||
move = 2 | ||
delete = 3 | ||
|
||
|
||
def upgrade(): | ||
op.execute(CreateSchema('plugin_zoom_rooms')) | ||
op.create_table( | ||
'queue', | ||
sa.Column('id', sa.Integer(), nullable=False), | ||
sa.Column('entry_id', sa.String(), nullable=False), | ||
sa.Column('zoom_room_id', sa.String(), nullable=False), | ||
sa.Column('action', PyIntEnum(_ZoomRoomsAction), nullable=False), | ||
sa.Column('entry_data', postgresql.JSONB(none_as_null=True, astext_type=sa.Text()), nullable=True), | ||
sa.Column('extra_args', postgresql.JSONB(none_as_null=True, astext_type=sa.Text()), nullable=True), | ||
sa.CheckConstraint( | ||
'action != 3 OR (entry_data IS NULL AND extra_args IS NULL)', name=op.f('ck_queue_delete_has_no_args') | ||
), | ||
sa.CheckConstraint('action = 2 OR extra_args IS NULL', name=op.f('ck_queue_move_has_extra_args')), | ||
sa.CheckConstraint('action = 2 OR extra_args IS NULL', name=op.f('ck_queue_other_actions_have_no_extra_args')), | ||
sa.CheckConstraint('action = 3 OR entry_data IS NOT NULL', name=op.f('ck_queue_other_actions_have_args')), | ||
sa.PrimaryKeyConstraint('id', name=op.f('pk_queue')), | ||
schema='plugin_zoom_rooms', | ||
) | ||
|
||
|
||
def downgrade(): | ||
op.drop_table('queue', schema='plugin_zoom_rooms') | ||
op.execute(DropSchema('plugin_zoom_rooms')) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
# This file is part of the CERN Indico plugins. | ||
# Copyright (C) 2024 CERN | ||
# | ||
# The CERN Indico plugins are free software; you can redistribute | ||
# them and/or modify them under the terms of the MIT License; see | ||
# the LICENSE file for more details. | ||
|
||
import typing as t | ||
|
||
from sqlalchemy.dialects.postgresql import JSONB | ||
|
||
from indico.core.db.sqlalchemy import PyIntEnum, db | ||
from indico.modules.events.contributions.models.contributions import Contribution | ||
from indico.modules.events.models.events import Event | ||
from indico.modules.events.sessions.models.blocks import SessionBlock | ||
from indico.modules.vc.models.vc_rooms import VCRoom, VCRoomEventAssociation | ||
from indico.util.enum import IndicoIntEnum | ||
|
||
from indico_zoom_rooms.util import make_zoom_room_entry_id | ||
|
||
|
||
class EntryData(t.TypedDict): | ||
start_dt: int | ||
end_dt: int | ||
title: str | ||
url: str | ||
|
||
|
||
class OperationArgs(t.TypedDict): | ||
start_dt: t.NotRequired[int] | ||
end_dt: t.NotRequired[int] | ||
new_zr_id: t.NotRequired[str] | ||
title: t.NotRequired[str] | ||
|
||
|
||
class ExtraArgs(t.TypedDict): | ||
new_zr_id: t.NotRequired[str] | ||
|
||
|
||
class ZoomRoomsAction(IndicoIntEnum): | ||
create = 0 | ||
update = 1 | ||
move = 2 | ||
delete = 3 | ||
|
||
|
||
def get_entry_data(obj: Event | Contribution | SessionBlock, vc_room: VCRoom) -> EntryData: | ||
if isinstance(obj, Event): | ||
return { | ||
'start_dt': int(obj.start_dt.timestamp()), | ||
'end_dt': int(obj.end_dt.timestamp()), | ||
'title': vc_room.name, | ||
'url': vc_room.data['url'], | ||
} | ||
else: | ||
entry = obj.timetable_entry | ||
return { | ||
'start_dt': int(entry.start_dt.timestamp()), | ||
'end_dt': int(entry.end_dt.timestamp()), | ||
'title': vc_room.name, | ||
'url': vc_room.data['url'], | ||
} | ||
|
||
|
||
class ZoomRoomsQueueEntry(db.Model): | ||
"""Pending calendar updates""" | ||
|
||
__tablename__ = 'queue' | ||
__table_args__ = ( | ||
db.CheckConstraint( | ||
f'action != {ZoomRoomsAction.delete} OR (entry_data IS NULL AND extra_args IS NULL)', 'delete_has_no_args' | ||
), | ||
db.CheckConstraint(f'action = {ZoomRoomsAction.delete} OR entry_data IS NOT NULL', 'other_actions_have_args'), | ||
db.CheckConstraint(f'action = {ZoomRoomsAction.move} OR extra_args IS NULL', 'move_has_extra_args'), | ||
db.CheckConstraint( | ||
f'action = {ZoomRoomsAction.move} OR extra_args IS NULL', 'other_actions_have_no_extra_args' | ||
), | ||
{'schema': 'plugin_zoom_rooms'}, | ||
) | ||
#: Entry ID (mainly used to sort by insertion order) | ||
id = db.Column(db.Integer, primary_key=True) | ||
#: ID of the Entry (to be used on the server side) | ||
entry_id = db.Column(db.String, nullable=False) | ||
#: ID of the Zoom Room | ||
zoom_room_id = db.Column(db.String, nullable=False) | ||
#: :class:`ZoomRoomsAction` to perform | ||
action = db.Column(PyIntEnum(ZoomRoomsAction), nullable=False) | ||
#: The actual entry's data | ||
entry_data: EntryData = db.Column( | ||
JSONB(none_as_null=True), | ||
) | ||
#: Additional args to be sent with the request | ||
extra_args: ExtraArgs = db.Column( | ||
JSONB(none_as_null=True), | ||
) | ||
|
||
def __repr__(self): | ||
action = ZoomRoomsAction(self.action).name | ||
return f'<ZoomRoomsQueueEntry({self.id}, {self.entry_id}, {action})>' | ||
|
||
@classmethod | ||
def record( | ||
cls, | ||
action: int, | ||
zoom_room_id: str, | ||
assoc: VCRoomEventAssociation | None = None, | ||
obj: Event | Contribution | SessionBlock | None = None, | ||
vc_room: VCRoom | None = None, | ||
args: OperationArgs | None = None, | ||
): | ||
if obj is None and assoc is not None: | ||
obj = assoc.link_object | ||
vc_room = vc_room or assoc.vc_room | ||
elif assoc is None and (obj is None or vc_room is None): | ||
raise ValueError('Either assoc or obj + vc_room must be provided') | ||
|
||
args = args or {} | ||
|
||
if new_zr_id := args.pop('new_zr_id', None): | ||
extra_args = {'new_zr_id': new_zr_id} | ||
else: | ||
extra_args = None | ||
|
||
entry = cls( | ||
action=action, | ||
entry_id=make_zoom_room_entry_id(zoom_room_id, obj, vc_room), | ||
# DELETE operations have no additional parameters, only the ID | ||
entry_data=None if action == ZoomRoomsAction.delete else EntryData(get_entry_data(obj, vc_room), **args), | ||
extra_args=extra_args, | ||
zoom_room_id=zoom_room_id, | ||
) | ||
db.session.add(entry) | ||
db.session.flush() | ||
|
||
@property | ||
def data(self) -> dict: | ||
return { | ||
'type': self.action, | ||
'entry_id': self.entry_id, | ||
'entry_data': self.entry_data, | ||
'zoom_room_id': self.zoom_room_id, | ||
'extra_args': self.extra_args, | ||
} |
Oops, something went wrong.