Add one off events for skipped meetings

Rather than silently removing the skipped meeting from the calendar as
is the default behavious of exdate add a one off calendar entry for
each skipped meeting.

The aim is to show that the slot is generally consumed just that the
meeting has been explictly cancelled/skipped.

Change-Id: Idd9eb1a04fe9c9661e87978c0128a292bb8e89a4
This commit is contained in:
Tony Breeds 2015-10-15 16:59:43 +11:00
parent ddf64aef6e
commit f02d892ea6
3 changed files with 68 additions and 23 deletions

View File

@ -258,14 +258,21 @@ meeting should be ommited from the ical schedule
VERSION:2.0
PRODID:-//yaml2ical agendas//EN
BEGIN:VEVENT
SUMMARY:CANCELLED: Example Project Meeting (20151026T210000Z)
DTSTART;VALUE=DATE-TIME:20151026T210000Z
DURATION:PT1H
DESCRIPTION:Face 2 Face meeting at some location
LOCATION:#openstack-meeting
END:VEVENT
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
EXDATE:20151026T210000Z
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/Meeting
s/Example\nProject URL: https://wiki.openstack.org/wiki/Example
LOCATION:#openstack-meeting
RRULE:FREQ=WEEKLY
END:VEVENT

View File

@ -34,14 +34,27 @@ class Yaml2IcalCalendar(icalendar.Calendar):
"""Add this meeting to the calendar."""
for sch in meeting.schedules:
# one Event per iCal file
self.add_schedule(meeting, sch)
def add_schedule(self, meeting, sch, exdate=None):
event = icalendar.Event()
# NOTE(jotan): I think the summary field needs to be unique per
# event in an ical file (at least, for it to work with
# Google Calendar)
event.add('summary', meeting.project)
summary = meeting.project
# NOTE(tonyb): If we're adding an a place holder event for a
# cancelled meeting make that as obvious as possible in the
# summary.
if exdate:
# NOTE(tonyb): Because some iCal consumers require that the
# summary be unique, and adding multiple "CANCELLED: $x"
# entries would violate that rule, append the (UTC)
# timestamp for the cancelled meeting.
suffix = exdate.date_str
summary = 'CANCELLED: %s (%s)' % (summary, suffix)
event.add('summary', summary)
event.add('location', '#' + sch.irc)
# add ical description
@ -60,28 +73,38 @@ class Yaml2IcalCalendar(icalendar.Calendar):
ical_descript = "\n".join((ical_descript,
"Project URL: %s" %
(meeting.extras['project_url'])))
event.add('description', ical_descript)
# get starting date
next_meeting = sch.recurrence.next_occurence(sch.start_date,
sch.day)
next_meeting_date = datetime.datetime(next_meeting.year,
next_meeting.month,
next_meeting.day,
sch.time.hour,
sch.time.minute,
tzinfo=pytz.utc)
event.add('dtstart', next_meeting_date)
# NOTE(tonyb): If we're adding an a place holder event for a
# cancelled meeting do not add an rrule and set dtstart to the
# skipped date.
if not exdate:
# get starting date
next_meeting = sch.recurrence.next_occurence(sch.start_date,
sch.day)
next_meeting_date = datetime.datetime(next_meeting.year,
next_meeting.month,
next_meeting.day,
sch.time.hour,
sch.time.minute,
tzinfo=pytz.utc)
event.add('dtstart', next_meeting_date)
# add recurrence rule
event.add('rrule', sch.recurrence.rrule())
# add recurrence rule
event.add('rrule', sch.recurrence.rrule())
event.add('description', ical_descript)
else:
event.add('dtstart', exdate.date)
event.add('description', exdate.reason)
event.add('duration', datetime.timedelta(minutes=sch.duration))
# Add exdate (exclude date) if present
if hasattr(sch, 'skip_dates'):
if not exdate and hasattr(sch, 'skip_dates'):
for skip_date in sch.skip_dates:
event.add('exdate', skip_date.date)
# NOTE(tonyb): If this is a skipped meeting add a
# non-recurring event with an obvisous summary.
self.add_schedule(meeting, sch, exdate=skip_date)
# add event to calendar
self.add_component(event)

View File

@ -94,12 +94,27 @@ class MeetingTestCase(unittest.TestCase):
def test_skip_meeting(self):
meeting_yaml = sample_data.MEETING_WITH_SKIP_DATES
p = re.compile('.*exdate:\s*20150810T120000', re.IGNORECASE)
# Copied from sample_data.MEETING_WITH_SKIP_DATES
summary = 'OpenStack Subteam 8 Meeting'
patterns = []
# The "main" meeting should have an exdate
patterns.append(re.compile('.*exdate:\s*20150810T120000', re.I))
# The "main" meeting should start on 2015-08-13
patterns.append(re.compile('.*dtstart;.*:20150803T120000Z', re.I))
# The "main" meeting should have a simple summary
patterns.append(re.compile('.*summary:\s*%s' % summary, re.I))
# The "skipped" meeting should start on 20150806
patterns.append(re.compile('.*dtstart;.*:20150810T120000Z', re.I))
# The "skipped" meeting should say include 'CANCELLED' and the datetime
patterns.append(re.compile('.*summary:\s*CANCELLED.*20150810T120000Z',
re.I))
m = meeting.load_meetings(meeting_yaml)[0]
cal = ical.Yaml2IcalCalendar()
cal.add_meeting(m)
cal_str = str(cal.to_ical())
self.assertTrue(hasattr(m.schedules[0], 'skip_dates'))
self.assertNotEqual(None, p.match(str(cal.to_ical())))
for p in patterns:
self.assertNotEqual(None, p.match(cal_str))
def test_skip_meeting_missing_skip_date(self):
self.assertRaises(KeyError,