Add support for skipping/excluding meetings

In the OpenStack community it's common to cancel meetings while the
summit happens.

Add the ability to add a list of dates to skip.  These will then be
included in the iCal

Change-Id: I1dd5ca6f3e5d6d257489fdc14bbe108abc7436e6
This commit is contained in:
Tony Breeds 2015-10-08 14:23:05 +11:00
parent faabbec1a6
commit ddf64aef6e
5 changed files with 203 additions and 2 deletions

View File

@ -123,6 +123,11 @@ Each meeting consists of:
* ``day``: the day of week the meeting takes place [MANDATORY]
* ``irc``: the irc room in which the meeting is held [MANDATORY]
* ``frequency``: frequent occurrence of the meeting [MANDATORY]
* ``skip_dates``: A set of dates that the meeting **DOES NOT** happen on
* ``skip_date``: Skip the meeting for specified date.
Format as ``start_date``
* ``reason``: A comment for why the meeting was skipped
* ``chair``: name of the meeting's chair [MANDATORY]
* ``description``: a paragraph description about the meeting [MANDATORY]
* ``agenda_url``: a link to the agenda page for the meeting
@ -137,8 +142,9 @@ templates, making it easy to build links to agenda pages for the
meeting or logs of past meetings. In the template file, use
``meeting.extras.name`` to access the value.
Example
-------
Example 1
---------
This is an example for the yaml meeting for Nova team meeting. The whole file
will be import into Python as a dictionary.
@ -204,3 +210,63 @@ will be import into Python as a dictionary.
::
project_url: https://wiki.openstack.org/wiki/Nova
* An extra property containing the MeetBot #startmeeting ID for the project is
saved in ``meeting_id`` and can be accessed in the template file as
``meeting.extras.meeting_id``.
::
meeting_id: nova
Example 2
---------
The following shows a complete YAML file for the IRC meetings for "example
project". The project starts holding weekly meetings from October 1st, the
project team has a "face to face" meeting on the 26th of October so that IRC
meeting should be ommited from the ical schedule
* This YAML
::
project: Example Project Meeting
project_url: https://wiki.openstack.org/wiki/Example
agenda_url: https://wiki.openstack.org/wiki/Meetings/Example
meeting_id: example
chair: A. Random Developer
description: >
This meeting is a weekly gathering of developers working on Example
project.
schedule:
- time: '2100'
day: Monday
irc: openstack-meeting
start_date: 20151001
frequency: weekly
skip_dates:
- skip_date: 20151026
reason: Face 2 Face meeting at some location
* Is converted into this iCal
::
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//yaml2ical agendas//EN
BEGIN:VEVENT
SUMMARY:Example Project Meeting
DTSTART;VALUE=DATE-TIME:20151005T210000Z
DURATION:PT1H
EXDATE:20151026T210000
DESCRIPTION:Project: Example Project Meeting\\nChair: A. Random Developer
\\nDescription: This meeting is a weekly gathering of developers working o
n Example project.\\n\\nAgenda URL: https://wiki.openstack.org/wiki/Meetin
gs/Example\\nProject URL: https://wiki.openstack.org/wiki/Example
LOCATION:#openstack-meeting
RRULE:FREQ=WEEKLY
END:VEVENT
END:VCALENDAR

View File

@ -78,6 +78,11 @@ class Yaml2IcalCalendar(icalendar.Calendar):
event.add('duration', datetime.timedelta(minutes=sch.duration))
# Add exdate (exclude date) if present
if hasattr(sch, 'skip_dates'):
for skip_date in sch.skip_dates:
event.add('exdate', skip_date.date)
# add event to calendar
self.add_component(event)

View File

@ -14,6 +14,7 @@ import datetime
from io import StringIO
import os
import os.path
import pytz
import yaml
@ -31,6 +32,19 @@ DATES = {
ONE_WEEK = datetime.timedelta(weeks=1)
class SkipDate(object):
"""A date, time and reason to skip a meeting."""
def __init__(self, date, time, reason):
date = datetime.datetime.combine(date, time).replace(tzinfo=pytz.utc)
self.date = date
self.reason = reason
@property
def date_str(self):
return self.date.strftime("%Y%m%dT%H%M%SZ")
class Schedule(object):
"""A meeting schedule."""
@ -77,6 +91,30 @@ class Schedule(object):
if self.day not in DATES.keys():
raise ValueError("'%s' is not a valid day of the week")
# optional: skip_dates
# This is a sequence of mappings (YAML)
# This is a list of dicts (python)
if 'skip_dates' in sched_yaml:
self.skip_dates = []
for skip_date in sched_yaml['skip_dates']:
missing_keys = set(['skip_date', 'reason']) - set(skip_date)
if missing_keys:
raise KeyError(("Processing: %s Missing keys - %s" %
(self.filefrom, ','.join(missing_keys))))
# NOTE(tonyb) We need to include the time in an exdate
# without it the excluded occurrence never matches a
# scheduled occurrence.
try:
date_str = str(skip_date['skip_date'])
date = datetime.datetime.strptime(date_str, '%Y%m%d')
self.skip_dates.append(SkipDate(date, self.time.time(),
skip_date['reason']))
except ValueError:
raise ValueError(("Processing: %s Could not parse "
"skip_date - %s" %
(self.filefrom, skip_date['skip_date'])))
# NOTE(tonyb): We need to do this datetime shenanigans is so we can
# deal with meetings that start on day1 and end on day2.
self.meeting_start = datetime.datetime.combine(DATES[self.day],

View File

@ -192,3 +192,69 @@ description: >
agenda: |
* Debate whether this should be a longer meeting
"""
MEETING_WITH_SKIP_DATES = """
project: OpenStack Subteam 8 Meeting
schedule:
- time: '1200'
day: Monday
start_date: 20150801
irc: openstack-meeting
frequency: weekly
skip_dates:
- skip_date: 20150810
reason: Chair on vacation
chair: Shannon Stacker
description: >
Weekly short meeting for Subteam project.
"""
MEETING_WITH_SKIP_DATES_BAD_DATE = """
project: OpenStack Subteam 8 Meeting
schedule:
- time: '1200'
day: Monday
start_date: 20150801
irc: openstack-meeting
frequency: weekly
skip_dates:
- skip_date: 2015080
reason: Chair on vacation
chair: Shannon Stacker
description: >
Weekly short meeting for Subteam project.
"""
# typo in skip_date
MEETING_WITH_MISSING_SKIP_DATE = """
project: OpenStack Subteam 8 Meeting
schedule:
- time: '1200'
day: Monday
start_date: 20150801
irc: openstack-meeting
frequency: weekly
skip_dates:
- skiip_date: 20150806
reason: Chair on vacation
chair: Shannon Stacker
description: >
Weekly short meeting for Subteam project.
"""
# typo in reason
MEETING_WITH_MISSING_REASON = """
project: OpenStack Subteam 8 Meeting
schedule:
- time: '1200'
day: Monday
start_date: 20150801
irc: openstack-meeting
frequency: weekly
skip_dates:
- skip_date: 20150806
reaso: Chair on vacation
chair: Shannon Stacker
description: >
Weekly short meeting for Subteam project.
"""

View File

@ -10,8 +10,10 @@
# License for the specific language governing permissions and limitations
# under the License.
import re
import unittest
from yaml2ical import ical
from yaml2ical import meeting
from yaml2ical.tests import sample_data
@ -89,3 +91,27 @@ class MeetingTestCase(unittest.TestCase):
self.should_not_conflict(
sample_data.CONFLICTING_WEEKLY_MEETING,
sample_data.MEETING_WITH_DURATION)
def test_skip_meeting(self):
meeting_yaml = sample_data.MEETING_WITH_SKIP_DATES
p = re.compile('.*exdate:\s*20150810T120000', re.IGNORECASE)
m = meeting.load_meetings(meeting_yaml)[0]
cal = ical.Yaml2IcalCalendar()
cal.add_meeting(m)
self.assertTrue(hasattr(m.schedules[0], 'skip_dates'))
self.assertNotEqual(None, p.match(str(cal.to_ical())))
def test_skip_meeting_missing_skip_date(self):
self.assertRaises(KeyError,
meeting.load_meetings,
sample_data.MEETING_WITH_MISSING_SKIP_DATE)
def test_skip_meeting_missing_reason(self):
self.assertRaises(KeyError,
meeting.load_meetings,
sample_data.MEETING_WITH_MISSING_REASON)
def test_skip_meeting_bad_skip_date(self):
self.assertRaises(ValueError,
meeting.load_meetings,
sample_data.MEETING_WITH_SKIP_DATES_BAD_DATE)