Skip to content

Commit 6b5bb6e

Browse files
adafiajoshuaocero
authored andcommitted
CV3-50-story(book-event): add book meeting functionality (#511)
- add book event class and mutate function - add helper to validate user date input - add test for added lines [Finishes CV3-50]
1 parent a38685b commit 6b5bb6e

File tree

7 files changed

+353
-16
lines changed

7 files changed

+353
-16
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,5 +140,5 @@ converge_db/
140140
events.json
141141
calendar_list.json
142142

143-
# maintain consistent coding styles
143+
# maintain consistent coding styles
144144
.editorconfig

api/events/schema.py

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@
1414
)
1515
from helpers.calendar.events import RoomSchedules, CalendarEvents
1616
from helpers.email.email import notification
17-
from helpers.calendar.credentials import get_single_calendar_event
17+
from helpers.calendar.credentials import (
18+
get_single_calendar_event,
19+
credentials
20+
)
1821
from helpers.auth.authentication import Auth
1922
from helpers.pagination.paginate import ListPaginate
2023
from helpers.devices.devices import update_device_last_activity
@@ -23,6 +26,10 @@
2326
validate_page_and_per_page,
2427
validate_calendar_id_input
2528
)
29+
from helpers.events_filter.events_filter import (
30+
calendar_dates_format,
31+
empty_string_checker
32+
)
2633

2734
utc = pytz.utc
2835

@@ -35,6 +42,87 @@ class Meta:
3542
model = EventsModel
3643

3744

45+
class BookEvent(graphene.Mutation):
46+
"""
47+
Book Calendar events
48+
"""
49+
response = graphene.String()
50+
51+
class Arguments:
52+
event_title = graphene.String(required=True)
53+
start_date = graphene.String(required=True)
54+
start_time = graphene.String(required=True)
55+
duration = graphene.Float(required=False)
56+
attendees = graphene.String(required=False)
57+
organizer = graphene.String(required=False)
58+
description = graphene.String(required=False)
59+
room = graphene.String(required=True)
60+
time_zone = graphene.String(required=True)
61+
62+
@Auth.user_roles('Admin', 'Default User', 'Super Admin')
63+
def mutate(self, info, **kwargs):
64+
"""Creates calendar events
65+
66+
Args:
67+
event_title: A sting that communicates the event summary/title
68+
[required]
69+
start_date: The start date of the event eg. 'Nov 4 2019' [required]
70+
start_time: The end time of the event eg '07:00 AM' [required]
71+
duration: A float of the duration of the event in minutes
72+
attendees: A string of emails of the event guests
73+
description: Any additional information about the event
74+
room: The meeting room where the even will be held [required]
75+
time_zone: The timezone of the event location eg. 'Africa/Kigali'
76+
organizer: The email of the co-organizer, the converge email is
77+
the default
78+
79+
Returns:
80+
A string that communicates a successfully created event.
81+
"""
82+
room = kwargs.get('room', None)
83+
attendees = kwargs.get('attendees', None)
84+
description = kwargs.get('description', None)
85+
time_zone = kwargs.get('time_zone', 'Africa/Accra')
86+
duration = kwargs.get('duration', 60)
87+
organizer = kwargs.get('organizer', None)
88+
89+
event_title = kwargs['event_title']
90+
empty_string_checker(event_title)
91+
empty_string_checker(room)
92+
empty_string_checker(time_zone)
93+
94+
start_date, end_date = calendar_dates_format(
95+
kwargs['start_date'], kwargs['start_time'], duration)
96+
97+
attendees = attendees.replace(" ", "").split(",")
98+
guests = []
99+
for guest in attendees:
100+
attendee = {'email': guest}
101+
guests.append(attendee)
102+
103+
event = {
104+
'summary': kwargs['event_title'],
105+
'location': room,
106+
'description': description,
107+
'start': {
108+
'dateTime': start_date,
109+
'timeZone': time_zone,
110+
},
111+
'end': {
112+
'dateTime': end_date,
113+
'timeZone': time_zone,
114+
},
115+
'attendees': guests,
116+
"organizer": {
117+
"email": organizer
118+
}
119+
}
120+
service = credentials.set_api_credentials()
121+
service.events().insert(calendarId='primary', body=event,
122+
sendNotifications=True).execute()
123+
return BookEvent(response='Event created successfully')
124+
125+
38126
class EventCheckin(graphene.Mutation):
39127
"""
40128
Returns the eventcheckin payload
@@ -204,6 +292,19 @@ def check_event_in_db(instance, info, event_check, **kwargs):
204292
class Mutation(graphene.ObjectType):
205293
event_checkin = EventCheckin.Field()
206294
cancel_event = CancelEvent.Field()
295+
book_event = BookEvent.Field(
296+
description="Mutation to book a calendar event given the arguments\
297+
\n- event_title: A sting that communicates the event\
298+
summary/title [required]\n- start_date: The start date \
299+
of the event eg 'Nov 4 2019' [required]\
300+
\n- start_time: The end time of the event eg'07:00 AM'[required]\
301+
\n- duration: A float of the duration of the event in minutes\
302+
\n- attendees: A string of emails of the event guests\
303+
\n- description: Any additional information about the event\
304+
\n- room: The meeting room where the even will be held[required]\
305+
\n- organizer: The email of the co - organizer, the converge\
306+
email is the default\
307+
\n- time_zone: The timezone of the event location")
207308
end_event = EndEvent.Field(
208309
description="Mutation to end a calendar event given the arguments\
209310
\n- calendar_id: The unique identifier of the calendar event\
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
book_event_mutation = '''mutation {
2+
bookEvent(
3+
eventTitle:"Hello Converge",
4+
startDate:"2029-11-06",
5+
startTime:"10:00",
6+
duration:60
7+
8+
timeZone: "Africa/Kigali",
9+
room:"Yankara"
10+
){
11+
response
12+
}
13+
}
14+
'''
15+
16+
book_event_mutation_no_title = '''mutation {
17+
bookEvent(
18+
eventTitle:"",
19+
startDate:"2019-11-06",
20+
startTime:"10:00",
21+
duration:60
22+
23+
timeZone: "Africa/Kigali",
24+
room:"Yankara"
25+
){
26+
response
27+
}
28+
}
29+
'''
30+
31+
book_event_mutation_no_room = '''mutation {
32+
bookEvent(
33+
eventTitle:"Hello Converge",
34+
startDate:"2019-11-06",
35+
startTime:"10:00",
36+
duration:60
37+
38+
timeZone: "Africa/Kigali",
39+
room:""
40+
){
41+
response
42+
}
43+
}
44+
'''
45+
46+
book_event_mutation_no_start_date = '''mutation {
47+
bookEvent(
48+
eventTitle:"Hello Converge",
49+
startDate:"",
50+
startTime:"10:00",
51+
duration:60
52+
53+
timeZone: "Africa/Kigali",
54+
room:"Yankara"
55+
){
56+
response
57+
}
58+
}
59+
'''
60+
61+
book_event_mutation_no_start_time = '''mutation {
62+
bookEvent(
63+
eventTitle:"Hello Converge",
64+
startDate:"2019-11-06",
65+
startTime:"",
66+
duration:60
67+
68+
timeZone: "Africa/Kigali",
69+
room:"Yankara"
70+
){
71+
response
72+
}
73+
}
74+
'''
75+
76+
book_event_mutation_no_time_Zone = '''mutation {
77+
bookEvent(
78+
eventTitle:"Hello Converge",
79+
startDate:"2019-11-06",
80+
startTime:"10:00",
81+
duration:60
82+
83+
timeZone: "",
84+
room:"Yankara"
85+
){
86+
response
87+
}
88+
}
89+
'''

fixtures/events/events_query_by_date_fixtures.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,11 @@
5151
'data': {
5252
'allEvents': {
5353
'events': [{
54-
'id': '1',
55-
'roomId': 1,
56-
'room': {
57-
'name': 'Entebbe'
58-
}
54+
'id': '1',
55+
'roomId': 1,
56+
'room': {
57+
'name': 'Entebbe'
58+
}
5959
}],
6060
'hasNext': False,
6161
'hasPrevious': False,
@@ -200,14 +200,14 @@
200200
'''
201201

202202
event_query_without_page_and_per_page_response = {
203-
'data': {
203+
'data': {
204204
'allEvents': {
205205
'events': [{
206-
'id': '1',
207-
'roomId': 1,
208-
'room': {
209-
'name': 'Entebbe'
210-
}
206+
'id': '1',
207+
'roomId': 1,
208+
'room': {
209+
'name': 'Entebbe'
210+
}
211211
}],
212212
'hasNext': None,
213213
'hasPrevious': None,

helpers/calendar/credentials.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ def set_api_credentials(self):
3737
return service
3838

3939

40+
credentials = Credentials()
41+
42+
4043
def get_google_calendar_events(calendarId=None, timeMin=None,
4144
timeMax=None, singleEvents=None, orderBy=None,
4245
syncToken=None, pageToken=None
@@ -78,7 +81,7 @@ def get_single_calendar_event(calendar_id, event_id):
7881
credentials = Credentials()
7982
service = credentials.set_api_credentials()
8083
event = service.events().get(
81-
calendarId=calendar_id,
82-
eventId=event_id
83-
).execute()
84+
calendarId=calendar_id,
85+
eventId=event_id
86+
).execute()
8487
return event

helpers/events_filter/events_filter.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from datetime import datetime, timedelta
22
from graphql import GraphQLError
33
import pytz
4+
import inspect
45
from dateutil import parser
56

67

@@ -63,3 +64,64 @@ def sort_events_by_date(events):
6364
events.sort(
6465
key=lambda x: parser.parse(x.start_time).astimezone(utc),
6566
reverse=True)
67+
68+
69+
def empty_string_checker(string_to_check):
70+
"""Raises a GraphQL error when an empty string is passed
71+
72+
Args:
73+
string_to_check: Any string that should not be empty
74+
"""
75+
frame = inspect.currentframe()
76+
info_data = inspect.getouterframes(frame, context=1)
77+
context = info_data[1].code_context[0].strip()
78+
error_title = context[context.index("(") + 1:context.rindex(")")]
79+
if not string_to_check:
80+
raise GraphQLError('{} can not be empty'.format(error_title))
81+
82+
83+
def date_time_format_validator(date_text, time_text):
84+
"""This function validates the date and time format
85+
86+
Args:
87+
start_date: Date string of the day of the event:'YY-MM-DD'
88+
start_time: Time sting of the time of the event: 'HH:MM'
89+
"""
90+
try:
91+
datetime.strptime(date_text, '%Y-%m-%d')
92+
except ValueError:
93+
raise ValueError("start date should be in this format: 'YY-MM-DD'")
94+
try:
95+
datetime.strptime(time_text, '%H:%M')
96+
except ValueError:
97+
raise ValueError("start time should be in this format: 'HH:MM'")
98+
99+
100+
def calendar_dates_format(start_date, start_time, duration):
101+
"""Converts user date, time and duration input into start_date
102+
and end_date format that is acceptable by the Google Calendar API
103+
104+
Args:
105+
start_date: Date string of the day of the event
106+
start_time: Time sting of the time of the event
107+
duration: A float of the duration of the event
108+
109+
Returns:
110+
start_date and end_date in this format "%Y-%m-%dT%H:%M:%S".
111+
"""
112+
empty_string_checker(start_date)
113+
empty_string_checker(start_time)
114+
date_time_format_validator(start_date, start_time)
115+
start_date = start_date + " " + start_time
116+
start_date = parser.parse(start_date)
117+
current_date = datetime.now()
118+
119+
if current_date > start_date:
120+
raise GraphQLError("Sorry time travel hasn't been invented yet")
121+
122+
end_date = start_date + timedelta(minutes=duration)
123+
124+
start_date = start_date.strftime('%Y-%m-%dT%H:%M:%S')
125+
end_date = end_date.strftime('%Y-%m-%dT%H:%M:%S')
126+
127+
return (start_date, end_date)

0 commit comments

Comments
 (0)