You've already forked oncall
mirror of
https://github.com/linkedin/oncall.git
synced 2025-12-02 23:58:38 +02:00
Refactor preview functionality to use temp tables (#194)
This commit is contained in:
committed by
Joe Gillotti
parent
025793a592
commit
57fae06277
@@ -13,6 +13,7 @@ def on_get(req, resp, schedule_id):
|
||||
Run the scheduler on demand from a given point in time. Unlike populate it doen't permanently delete or insert anything.
|
||||
"""
|
||||
start_time = float(req.get_param('start', required=True))
|
||||
table_name = 'temp_event'
|
||||
|
||||
connection = db.connect()
|
||||
cursor = connection.cursor(db.DictCursor)
|
||||
@@ -26,6 +27,14 @@ def on_get(req, resp, schedule_id):
|
||||
scheduler = load_scheduler(scheduler_name)
|
||||
schedule = get_schedules({'id': schedule_id})[0]
|
||||
check_team_auth(schedule['team'], req)
|
||||
scheduler.preview(schedule, start_time, (connection, cursor), req, resp)
|
||||
|
||||
start__lt = req.get_param('start__lt', required=True)
|
||||
end__ge = req.get_param('end__ge', required=True)
|
||||
team__eq = req.get_param('team__eq', required=True)
|
||||
|
||||
cursor.execute('CREATE TEMPORARY TABLE IF NOT EXISTS `temp_event` AS (SELECT * FROM `event` WHERE `start` < %s AND `end` > %s)', (start__lt, end__ge))
|
||||
|
||||
scheduler.populate(schedule, start_time, (connection, cursor), table_name)
|
||||
resp.body = scheduler.build_preview_response(cursor, start__lt, end__ge, team__eq, table_name)
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
@@ -14,56 +14,22 @@ SECONDS_IN_A_DAY = 24 * 60 * 60
|
||||
SECONDS_IN_A_WEEK = SECONDS_IN_A_DAY * 7
|
||||
|
||||
columns = {
|
||||
'id': '`event`.`id` as `id`',
|
||||
'start': '`event`.`start` as `start`',
|
||||
'end': '`event`.`end` as `end`',
|
||||
'id': '`temp_event`.`id` as `id`',
|
||||
'start': '`temp_event`.`start` as `start`',
|
||||
'end': '`temp_event`.`end` as `end`',
|
||||
'role': '`role`.`name` as `role`',
|
||||
'team': '`team`.`name` as `team`',
|
||||
'user': '`user`.`name` as `user`',
|
||||
'full_name': '`user`.`full_name` as `full_name`',
|
||||
'schedule_id': '`event`.`schedule_id`',
|
||||
'link_id': '`event`.`link_id`',
|
||||
'note': '`event`.`note`',
|
||||
'schedule_id': '`temp_event`.`schedule_id`',
|
||||
'link_id': '`temp_event`.`link_id`',
|
||||
'note': '`temp_event`.`note`',
|
||||
}
|
||||
|
||||
constraints = {
|
||||
'id': '`event`.`id` = %s',
|
||||
'id__eq': '`event`.`id` = %s',
|
||||
'id__ne': '`event`.`id` != %s',
|
||||
'id__gt': '`event`.`id` > %s',
|
||||
'id__ge': '`event`.`id` >= %s',
|
||||
'id__lt': '`event`.`id` < %s',
|
||||
'id__le': '`event`.`id` <= %s',
|
||||
'start': '`event`.`start` = %s',
|
||||
'start__eq': '`event`.`start` = %s',
|
||||
'start__ne': '`event`.`start` != %s',
|
||||
'start__gt': '`event`.`start` > %s',
|
||||
'start__ge': '`event`.`start` >= %s',
|
||||
'start__lt': '`event`.`start` < %s',
|
||||
'start__le': '`event`.`start` <= %s',
|
||||
'end': '`event`.`end` = %s',
|
||||
'end__eq': '`event`.`end` = %s',
|
||||
'end__ne': '`event`.`end` != %s',
|
||||
'end__gt': '`event`.`end` > %s',
|
||||
'end__ge': '`event`.`end` >= %s',
|
||||
'end__lt': '`event`.`end` < %s',
|
||||
'end__le': '`event`.`end` <= %s',
|
||||
'role': '`role`.`name` = %s',
|
||||
'role__eq': '`role`.`name` = %s',
|
||||
'role__contains': '`role`.`name` LIKE CONCAT("%%", %s, "%%")',
|
||||
'role__startswith': '`role`.`name` LIKE CONCAT(%s, "%%")',
|
||||
'role__endswith': '`role`.`name` LIKE CONCAT("%%", %s)',
|
||||
'team': '`team`.`name` = %s',
|
||||
'start__lt': '`temp_event`.`start` < %s',
|
||||
'end__ge': '`temp_event`.`end` >= %s',
|
||||
'team__eq': '`team`.`name` = %s',
|
||||
'team__contains': '`team`.`name` LIKE CONCAT("%%", %s, "%%")',
|
||||
'team__startswith': '`team`.`name` LIKE CONCAT(%s, "%%")',
|
||||
'team__endswith': '`team`.`name` LIKE CONCAT("%%", %s)',
|
||||
'team_id': '`team`.`id` = %s',
|
||||
'user': '`user`.`name` = %s',
|
||||
'user__eq': '`user`.`name` = %s',
|
||||
'user__contains': '`user`.`name` LIKE CONCAT("%%", %s, "%%")',
|
||||
'user__startswith': '`user`.`name` LIKE CONCAT(%s, "%%")',
|
||||
'user__endswith': '`user`.`name` LIKE CONCAT("%%", %s)'
|
||||
}
|
||||
|
||||
all_columns = ', '.join(columns.values())
|
||||
@@ -79,9 +45,9 @@ class Scheduler(object):
|
||||
role_id = cursor.fetchone()['id']
|
||||
return role_id
|
||||
|
||||
def get_schedule_last_event_end(self, schedule, cursor):
|
||||
cursor.execute('SELECT `end` FROM `event` WHERE `schedule_id` = %r ORDER BY `end` DESC LIMIT 1',
|
||||
schedule['id'])
|
||||
def get_schedule_last_event_end(self, schedule, cursor, table_name='event'):
|
||||
query = 'SELECT `end` FROM `%s` WHERE `schedule_id` = %%r ORDER BY `end` DESC LIMIT 1' % table_name
|
||||
cursor.execute(query, schedule['id'])
|
||||
if cursor.rowcount != 0:
|
||||
return cursor.fetchone()['end']
|
||||
else:
|
||||
@@ -103,7 +69,7 @@ class Scheduler(object):
|
||||
AND `user`.`active` = TRUE''', roster_id)
|
||||
return [r['user_id'] for r in cursor]
|
||||
|
||||
def get_busy_user_by_event_range(self, user_ids, team_id, events, cursor):
|
||||
def get_busy_user_by_event_range(self, user_ids, team_id, events, cursor, table_name='event'):
|
||||
''' Find which users have overlapping events for the same team in this time range'''
|
||||
query_params = [user_ids]
|
||||
range_check = []
|
||||
@@ -123,50 +89,53 @@ class Scheduler(object):
|
||||
query_params += [sub['subscription_id'], sub['role_id']]
|
||||
|
||||
query = '''
|
||||
SELECT DISTINCT `user_id` FROM `event`
|
||||
SELECT DISTINCT `user_id` FROM `%s`
|
||||
WHERE `user_id` in %%s AND (%s) AND (%s)
|
||||
''' % (' OR '.join(range_check), ' OR '.join(team_check))
|
||||
''' % (table_name, ' OR '.join(range_check), ' OR '.join(team_check))
|
||||
|
||||
cursor.execute(query, query_params)
|
||||
return [r['user_id'] for r in cursor.fetchall()]
|
||||
|
||||
def find_least_active_user_id_by_team(self, user_ids, team_id, start_time, role_id, cursor):
|
||||
def find_least_active_user_id_by_team(self, user_ids, team_id, start_time, role_id, cursor, table_name='event'):
|
||||
'''
|
||||
Of the people who have been oncall before, finds those who haven't been oncall for the longest. Start
|
||||
time refers to the start time of the event being created, so we don't accidentally look at future
|
||||
events when determining who was oncall in the past. Done on a per-role basis, so we don't take manager
|
||||
or vacation shifts into account
|
||||
'''
|
||||
cursor.execute('''
|
||||
SELECT `user_id`, MAX(`end`) AS `last_end` FROM `event`
|
||||
WHERE `team_id` = %s AND `user_id` IN %s AND `end` <= %s
|
||||
AND `role_id` = %s
|
||||
query = '''
|
||||
SELECT `user_id`, MAX(`end`) AS `last_end` FROM `%s`
|
||||
WHERE `team_id` = %%s AND `user_id` IN %%s AND `end` <= %%s
|
||||
AND `role_id` = %%s
|
||||
GROUP BY `user_id`
|
||||
''', (team_id, user_ids, start_time, role_id))
|
||||
''' % table_name
|
||||
|
||||
cursor.execute(query, (team_id, user_ids, start_time, role_id))
|
||||
if cursor.rowcount != 0:
|
||||
# Grab user id with lowest last scheduled time
|
||||
return min(cursor.fetchall(), key=operator.itemgetter('last_end'))['user_id']
|
||||
else:
|
||||
return None
|
||||
|
||||
def find_new_user_in_roster(self, roster_id, team_id, start_time, role_id, cursor):
|
||||
def find_new_user_in_roster(self, roster_id, team_id, start_time, role_id, cursor, table_name='event'):
|
||||
'''
|
||||
Return roster users who haven't been scheduled for any event on this team's calendar for this schedule's role.
|
||||
Ignores events from other teams.
|
||||
'''
|
||||
query = '''
|
||||
SELECT DISTINCT `user`.`id` FROM `roster_user`
|
||||
JOIN `user` ON `user`.`id` = `roster_user`.`user_id` AND `roster_user`.`roster_id` = %s
|
||||
LEFT JOIN `event` ON `event`.`user_id` = `user`.`id` AND `event`.`team_id` = %s AND `event`.`end` <= %s
|
||||
AND `event`.`role_id` = %s
|
||||
WHERE `roster_user`.`in_rotation` = 1 AND `event`.`id` IS NULL
|
||||
'''
|
||||
JOIN `user` ON `user`.`id` = `roster_user`.`user_id` AND `roster_user`.`roster_id` = %%s
|
||||
LEFT JOIN `%s` ON `%s`.`user_id` = `user`.`id` AND `%s`.`team_id` = %%s AND `%s`.`end` <= %%s
|
||||
AND `%s`.`role_id` = %%s
|
||||
WHERE `roster_user`.`in_rotation` = 1 AND `%s`.`id` IS NULL
|
||||
''' % (table_name, table_name, table_name, table_name, table_name, table_name)
|
||||
|
||||
cursor.execute(query, (roster_id, team_id, start_time, role_id))
|
||||
if cursor.rowcount != 0:
|
||||
logger.debug('Found new guy')
|
||||
return {row['id'] for row in cursor}
|
||||
|
||||
def create_events(self, team_id, schedule_id, user_id, events, role_id, cursor, skip_match=True):
|
||||
def create_events(self, team_id, schedule_id, user_id, events, role_id, cursor, skip_match=True, table_name='event'):
|
||||
if len(events) == 0:
|
||||
return
|
||||
# Skip creating this epoch of events if matching events exist
|
||||
@@ -176,7 +145,10 @@ class Scheduler(object):
|
||||
|
||||
for ev in events:
|
||||
query_params += [ev['start'], ev['end'], role_id, team_id]
|
||||
cursor.execute('SELECT COUNT(*) AS num_events FROM event WHERE %s' % matching, query_params)
|
||||
|
||||
query = 'SELECT COUNT(*) AS num_events FROM %s WHERE %s' % (table_name, matching)
|
||||
|
||||
cursor.execute(query, query_params)
|
||||
if cursor.fetchone()['num_events'] == len(events):
|
||||
return
|
||||
|
||||
@@ -185,11 +157,11 @@ class Scheduler(object):
|
||||
event_args = (team_id, schedule_id, event['start'], event['end'], user_id, role_id)
|
||||
logger.debug('inserting event: %s', event_args)
|
||||
query = '''
|
||||
INSERT INTO `event` (
|
||||
INSERT INTO `%s` (
|
||||
`team_id`, `schedule_id`, `start`, `end`, `user_id`, `role_id`
|
||||
) VALUES (
|
||||
%s, %s, %s, %s, %s, %s
|
||||
)'''
|
||||
%%s, %%s, %%s, %%s, %%s, %%s
|
||||
)''' % table_name
|
||||
cursor.execute(query, event_args)
|
||||
else:
|
||||
link_id = gen_link_id()
|
||||
@@ -197,11 +169,11 @@ class Scheduler(object):
|
||||
event_args = (team_id, schedule_id, event['start'], event['end'], user_id, role_id, link_id)
|
||||
logger.debug('inserting event: %s', event_args)
|
||||
query = '''
|
||||
INSERT INTO `event` (
|
||||
INSERT INTO `%s` (
|
||||
`team_id`, `schedule_id`, `start`, `end`, `user_id`, `role_id`, `link_id`
|
||||
) VALUES (
|
||||
%s, %s, %s, %s, %s, %s, %s
|
||||
)'''
|
||||
%%s, %%s, %%s, %%s, %%s, %%s, %%s
|
||||
)''' % table_name
|
||||
cursor.execute(query, event_args)
|
||||
|
||||
def set_last_epoch(self, schedule_id, last_epoch, cursor):
|
||||
@@ -311,7 +283,7 @@ class Scheduler(object):
|
||||
# Return future events and the last epoch events were scheduled for.
|
||||
return future_events, self.utc_from_naive_date(next_epoch - timedelta(days=7 * period), schedule)
|
||||
|
||||
def find_next_user_id(self, schedule, future_events, cursor):
|
||||
def find_next_user_id(self, schedule, future_events, cursor, table_name='event'):
|
||||
team_id = schedule['team_id']
|
||||
role_id = schedule['role_id']
|
||||
roster_id = schedule['roster_id']
|
||||
@@ -323,19 +295,19 @@ class Scheduler(object):
|
||||
return None
|
||||
logger.debug('filtering users: %s', user_ids)
|
||||
start = min([e['start'] for e in future_events])
|
||||
for uid in self.get_busy_user_by_event_range(user_ids, team_id, future_events, cursor):
|
||||
for uid in self.get_busy_user_by_event_range(user_ids, team_id, future_events, cursor, table_name):
|
||||
user_ids.remove(uid)
|
||||
if not user_ids:
|
||||
logger.info('All users have conflicting events, skipping...')
|
||||
return None
|
||||
new_user_ids = self.find_new_user_in_roster(roster_id, team_id, start, role_id, cursor)
|
||||
new_user_ids = self.find_new_user_in_roster(roster_id, team_id, start, role_id, cursor, table_name)
|
||||
available_and_new = new_user_ids & user_ids
|
||||
if available_and_new:
|
||||
logger.info('Picking new and available user from %s', available_and_new)
|
||||
return available_and_new.pop()
|
||||
|
||||
logger.debug('picking user between: %s, team: %s', user_ids, team_id)
|
||||
return self.find_least_active_user_id_by_team(user_ids, team_id, start, role_id, cursor)
|
||||
return self.find_least_active_user_id_by_team(user_ids, team_id, start, role_id, cursor, table_name)
|
||||
|
||||
def schedule(self, team, schedules, dbinfo):
|
||||
connection, cursor = dbinfo
|
||||
@@ -364,77 +336,20 @@ class Scheduler(object):
|
||||
self.create_events(team['id'], schedule['id'], user_id, epoch, schedule['role_id'], cursor)
|
||||
connection.commit()
|
||||
|
||||
def preview(self, schedule, start_time, dbinfo, req, resp):
|
||||
|
||||
connection, cursor = dbinfo
|
||||
delete_list = []
|
||||
response_list = []
|
||||
start_dt = datetime.fromtimestamp(start_time, utc)
|
||||
start_epoch = self.epoch_from_datetime(start_dt)
|
||||
|
||||
# Get schedule info
|
||||
role_id = schedule['role_id']
|
||||
team_id = schedule['team_id']
|
||||
first_event_start = min(ev['start'] for ev in schedule['events'])
|
||||
period = self.get_period_len(schedule)
|
||||
handoff = start_epoch + timedelta(seconds=first_event_start)
|
||||
handoff = timezone(schedule['timezone']).localize(handoff)
|
||||
|
||||
# Start scheduling from the next occurrence of the hand-off time.
|
||||
if start_dt > handoff:
|
||||
start_epoch += timedelta(weeks=period)
|
||||
handoff += timedelta(weeks=period)
|
||||
if handoff < utc.localize(datetime.utcnow()):
|
||||
raise HTTPBadRequest('Invalid populate request', 'cannot populate starting in the past')
|
||||
|
||||
future_events, last_epoch = self.calculate_future_events(schedule, cursor, start_epoch)
|
||||
self.set_last_epoch(schedule['id'], last_epoch, cursor)
|
||||
|
||||
# Delete existing events from the start of the first event
|
||||
future_events = [filter(lambda x: x['start'] >= start_time, evs) for evs in future_events]
|
||||
future_events = filter(lambda x: x != [], future_events)
|
||||
if future_events:
|
||||
first_event_start = min(future_events[0], key=lambda x: x['start'])['start']
|
||||
# store the events that will be deleted so they can get aded back in later
|
||||
cursor.execute('SELECT * FROM event WHERE schedule_id = %s AND start >= %s', (schedule['id'], first_event_start))
|
||||
delete_list = cursor.fetchall()
|
||||
cursor.execute('DELETE FROM event WHERE schedule_id = %s AND start >= %s', (schedule['id'], first_event_start))
|
||||
|
||||
# Create events in the db, associating a user to them
|
||||
for epoch in future_events:
|
||||
user_id = self.find_next_user_id(schedule, epoch, cursor)
|
||||
if not user_id:
|
||||
continue
|
||||
|
||||
self.create_events(team_id, schedule['id'], user_id, epoch, role_id, cursor)
|
||||
|
||||
def build_preview_response(self, cursor, start__lt, end__ge, team__eq, table_name='temp_event'):
|
||||
# get existing events
|
||||
|
||||
cols = all_columns
|
||||
query = '''SELECT %s FROM `event`
|
||||
JOIN `user` ON `user`.`id` = `event`.`user_id`
|
||||
JOIN `team` ON `team`.`id` = `event`.`team_id`
|
||||
JOIN `role` ON `role`.`id` = `event`.`role_id`''' % cols
|
||||
where_params = []
|
||||
where_vals = []
|
||||
|
||||
# Build where clause. If including subscriptions, deal with team parameters later
|
||||
params = {'start__lt': req.get_param('start__lt', required=True), 'end__ge': req.get_param('end__ge', required=True)}
|
||||
|
||||
for key in params:
|
||||
val = req.get_param(key)
|
||||
where_params.append(constraints[key])
|
||||
where_vals.append(val)
|
||||
query = '''SELECT %s FROM `%s`
|
||||
JOIN `user` ON `user`.`id` = `%s`.`user_id`
|
||||
JOIN `team` ON `team`.`id` = `%s`.`team_id`
|
||||
JOIN `role` ON `role`.`id` = `%s`.`role_id`''' % (cols, table_name, table_name, table_name, table_name)
|
||||
where_params = [constraints['start__lt'], constraints['end__ge']]
|
||||
where_vals = [start__lt, end__ge]
|
||||
|
||||
# Deal with team subscriptions and team parameters
|
||||
team_where = []
|
||||
subs_vals = []
|
||||
team_params = {'team__eq': req.get_param('team__eq', required=True)}
|
||||
|
||||
for key in team_params:
|
||||
val = req.get_param(key)
|
||||
team_where.append(constraints[key])
|
||||
subs_vals.append(val)
|
||||
team_where = [constraints['team__eq']]
|
||||
subs_vals = [team__eq]
|
||||
subs_and = ' AND '.join(team_where)
|
||||
cursor.execute('''SELECT `subscription_id`, `role_id` FROM `team_subscription`
|
||||
JOIN `team` ON `team_id` = `team`.`id`
|
||||
@@ -451,29 +366,9 @@ class Scheduler(object):
|
||||
query = '%s WHERE %s' % (query, where_query)
|
||||
cursor.execute(query, where_vals)
|
||||
data = cursor.fetchall()
|
||||
response_list = data
|
||||
return json_dumps(data)
|
||||
|
||||
# delete new inserted events
|
||||
if future_events:
|
||||
first_event_start = min(future_events[0], key=lambda x: x['start'])['start']
|
||||
cursor.execute('DELETE FROM event WHERE schedule_id = %s AND start >= %s', (schedule['id'], first_event_start))
|
||||
|
||||
# re insert deleted events
|
||||
for event in delete_list:
|
||||
event_args = (event['user_id'], event['schedule_id'], event['link_id'], event['note'], event['start'],
|
||||
event['team_id'], event['end'], event['role_id'], event['id'])
|
||||
logger.debug('inserting event: %s', event_args)
|
||||
query = '''
|
||||
INSERT INTO `event` (
|
||||
`user_id`, `schedule_id`, `link_id`, `note`, `start`, `team_id`, `end`,`role_id`, `id`
|
||||
) VALUES (
|
||||
%s, %s, %s, %s, %s, %s, %s, %s, %s
|
||||
)'''
|
||||
cursor.execute(query, event_args)
|
||||
|
||||
resp.body = json_dumps(response_list)
|
||||
|
||||
def populate(self, schedule, start_time, dbinfo):
|
||||
def populate(self, schedule, start_time, dbinfo, table_name='event'):
|
||||
connection, cursor = dbinfo
|
||||
start_dt = datetime.fromtimestamp(start_time, utc)
|
||||
start_epoch = self.epoch_from_datetime(start_dt)
|
||||
@@ -501,12 +396,13 @@ class Scheduler(object):
|
||||
future_events = filter(lambda x: x != [], future_events)
|
||||
if future_events:
|
||||
first_event_start = min(future_events[0], key=lambda x: x['start'])['start']
|
||||
cursor.execute('DELETE FROM event WHERE schedule_id = %s AND start >= %s', (schedule['id'], first_event_start))
|
||||
query = 'DELETE FROM %s WHERE schedule_id = %%s AND start >= %%s' % table_name
|
||||
cursor.execute(query, (schedule['id'], first_event_start))
|
||||
|
||||
# Create events in the db, associating a user to them
|
||||
for epoch in future_events:
|
||||
user_id = self.find_next_user_id(schedule, epoch, cursor)
|
||||
user_id = self.find_next_user_id(schedule, epoch, cursor, table_name)
|
||||
if not user_id:
|
||||
continue
|
||||
self.create_events(team_id, schedule['id'], user_id, epoch, role_id, cursor)
|
||||
self.create_events(team_id, schedule['id'], user_id, epoch, role_id, cursor, table_name=table_name)
|
||||
connection.commit()
|
||||
@@ -2,5 +2,5 @@ import default
|
||||
|
||||
|
||||
class Scheduler(default.Scheduler):
|
||||
def create_events(self, team_id, schedule_id, user_id, events, role_id, cursor, skip_match=True):
|
||||
super(Scheduler, self).create_events(team_id, schedule_id, user_id, events, role_id, cursor, skip_match=False)
|
||||
def create_events(self, team_id, schedule_id, user_id, events, role_id, cursor, skip_match=True, table_name='event'):
|
||||
super(Scheduler, self).create_events(team_id, schedule_id, user_id, events, role_id, cursor, skip_match=False, table_name='event')
|
||||
@@ -7,22 +7,23 @@ logger = logging.getLogger()
|
||||
|
||||
class Scheduler(default.Scheduler):
|
||||
|
||||
def guess_last_scheduled_user(self, schedule, start, roster, cursor):
|
||||
cursor.execute('''
|
||||
def guess_last_scheduled_user(self, schedule, start, roster, cursor, table_name='event'):
|
||||
query = '''
|
||||
SELECT `last_start`, `user_id` FROM
|
||||
(SELECT `user_id`, MAX(`start`) AS `last_start` FROM `event`
|
||||
WHERE `team_id` = %s AND `user_id` IN %s AND `start` <= %s
|
||||
AND `role_id` = %s
|
||||
(SELECT `user_id`, MAX(`start`) AS `last_start` FROM `%s`
|
||||
WHERE `team_id` = %%s AND `user_id` IN %%s AND `start` <= %%s
|
||||
AND `role_id` = %%s
|
||||
GROUP BY `user_id`
|
||||
ORDER BY `last_start` DESC) t
|
||||
LIMIT 1
|
||||
''', (schedule['team_id'], roster, start, schedule['role_id']))
|
||||
''' % table_name
|
||||
cursor.execute(query, (schedule['team_id'], roster, start, schedule['role_id']))
|
||||
if cursor.rowcount != 0:
|
||||
return cursor.fetchone()['user_id']
|
||||
else:
|
||||
return None
|
||||
|
||||
def find_next_user_id(self, schedule, future_events, cursor):
|
||||
def find_next_user_id(self, schedule, future_events, cursor, table_name='event'):
|
||||
cursor.execute('''SELECT `user_id` FROM `roster_user`
|
||||
WHERE `roster_id` = %s AND in_rotation = TRUE''',
|
||||
schedule['roster_id'])
|
||||
@@ -42,24 +43,24 @@ class Scheduler(default.Scheduler):
|
||||
# If this user is no longer in the roster or last_scheduled_user is NULL, try to find
|
||||
# the last scheduled user using the calendar
|
||||
start = min(e['start'] for e in future_events)
|
||||
last_user = self.guess_last_scheduled_user(schedule, start, roster, cursor)
|
||||
last_user = self.guess_last_scheduled_user(schedule, start, roster, cursor, table_name)
|
||||
if last_user is None:
|
||||
# If this doesn't work, return the first user in the roster
|
||||
return roster[0]
|
||||
last_idx = roster.index(last_user)
|
||||
return roster[(last_idx + 1) % len(roster)]
|
||||
|
||||
def create_events(self, team_id, schedule_id, user_id, events, role_id, cursor, skip_match=True):
|
||||
def create_events(self, team_id, schedule_id, user_id, events, role_id, cursor, skip_match=True, table_name='event'):
|
||||
if len(events) == 1:
|
||||
[event] = events
|
||||
event_args = (team_id, schedule_id, event['start'], event['end'], user_id, role_id)
|
||||
logger.debug('inserting event: %s', event_args)
|
||||
query = '''
|
||||
INSERT INTO `event` (
|
||||
INSERT INTO `%s` (
|
||||
`team_id`, `schedule_id`, `start`, `end`, `user_id`, `role_id`
|
||||
) VALUES (
|
||||
%s, %s, %s, %s, %s, %s
|
||||
)'''
|
||||
%%s, %%s, %%s, %%s, %%s, %%s
|
||||
)''' % table_name
|
||||
cursor.execute(query, event_args)
|
||||
else:
|
||||
link_id = gen_link_id()
|
||||
@@ -67,22 +68,16 @@ class Scheduler(default.Scheduler):
|
||||
event_args = (team_id, schedule_id, event['start'], event['end'], user_id, role_id, link_id)
|
||||
logger.debug('inserting event: %s', event_args)
|
||||
query = '''
|
||||
INSERT INTO `event` (
|
||||
INSERT INTO `%s` (
|
||||
`team_id`, `schedule_id`, `start`, `end`, `user_id`, `role_id`, `link_id`
|
||||
) VALUES (
|
||||
%s, %s, %s, %s, %s, %s, %s
|
||||
)'''
|
||||
%%s, %%s, %%s, %%s, %%s, %%s, %%s
|
||||
)''' % table_name
|
||||
cursor.execute(query, event_args)
|
||||
cursor.execute('UPDATE `schedule` SET `last_scheduled_user_id` = %s WHERE `id` = %s', (user_id, schedule_id))
|
||||
|
||||
def populate(self, schedule, start_time, dbinfo):
|
||||
def populate(self, schedule, start_time, dbinfo, table_name='event'):
|
||||
_, cursor = dbinfo
|
||||
# Null last_scheduled_user to force find_next_user to determine that from the calendar
|
||||
cursor.execute('UPDATE `schedule` SET `last_scheduled_user_id` = NULL WHERE `id` = %s', schedule['id'])
|
||||
super(Scheduler, self).populate(schedule, start_time, dbinfo)
|
||||
|
||||
def preview(self, schedule, start_time, dbinfo, req, resp):
|
||||
_, cursor = dbinfo
|
||||
# Null last_scheduled_user to force find_next_user to determine that from the calendar
|
||||
cursor.execute('UPDATE `schedule` SET `last_scheduled_user_id` = NULL WHERE `id` = %s', schedule['id'])
|
||||
super(Scheduler, self).preview(schedule, start_time, dbinfo, req, resp)
|
||||
super(Scheduler, self).populate(schedule, start_time, dbinfo, table_name='event')
|
||||
@@ -242,7 +242,7 @@ def test_find_least_active_available_user(mocker):
|
||||
mock_busy_user_by_range = mocker.patch('oncall.scheduler.default.Scheduler.get_busy_user_by_event_range')
|
||||
mock_active_user_by_team = mocker.patch('oncall.scheduler.default.Scheduler.find_least_active_user_id_by_team')
|
||||
|
||||
def mock_busy_user_by_range_side_effect(user_ids, team_id, events, cursor):
|
||||
def mock_busy_user_by_range_side_effect(user_ids, team_id, events, cursor, table_name='event'):
|
||||
assert user_ids == set(mock_user_ids)
|
||||
return [123]
|
||||
|
||||
@@ -251,9 +251,9 @@ def test_find_least_active_available_user(mocker):
|
||||
{'start': 570, 'end': 588},
|
||||
{'start': 600, 'end': 700}]
|
||||
scheduler = oncall.scheduler.default.Scheduler()
|
||||
scheduler.find_next_user_id(MOCK_SCHEDULE, future_events, None)
|
||||
scheduler.find_next_user_id(MOCK_SCHEDULE, future_events, None, 'event')
|
||||
|
||||
mock_active_user_by_team.assert_called_with({456, 789}, 1, 440, 2, None)
|
||||
mock_active_user_by_team.assert_called_with({456, 789}, 1, 440, 2, None, 'event')
|
||||
|
||||
|
||||
def test_find_least_active_available_user_conflicts(mocker):
|
||||
@@ -263,13 +263,13 @@ def test_find_least_active_available_user_conflicts(mocker):
|
||||
mock_busy_user_by_range = mocker.patch('oncall.scheduler.default.Scheduler.get_busy_user_by_event_range')
|
||||
mock_active_user_by_team = mocker.patch('oncall.scheduler.default.Scheduler.find_least_active_user_id_by_team')
|
||||
|
||||
def mock_busy_user_by_range_side_effect(user_ids, team_id, events, cursor):
|
||||
def mock_busy_user_by_range_side_effect(user_ids, team_id, events, cursor, table_name='event'):
|
||||
assert user_ids == set(mock_user_ids)
|
||||
return [123, 456, 789]
|
||||
|
||||
mock_busy_user_by_range.side_effect = mock_busy_user_by_range_side_effect
|
||||
future_events = [{'start': 440, 'end': 570}]
|
||||
scheduler = oncall.scheduler.default.Scheduler()
|
||||
assert scheduler.find_next_user_id(MOCK_SCHEDULE, future_events, None) is None
|
||||
assert scheduler.find_next_user_id(MOCK_SCHEDULE, future_events, None, table_name='event') is None
|
||||
|
||||
mock_active_user_by_team.assert_not_called()
|
||||
|
||||
Reference in New Issue
Block a user