def resolve_comments(self, session, event): # Sanity check. If there's no comment_id, exit. if 'comment_id' not in event: return None # Retrieve the content of the comment. comment = db_api.entity_get(models.Comment, event['comment_id'], session=session) if not comment: return None # Retrieve the timeline events. timeline_event = session.query(models.TimeLineEvent) \ .filter(models.TimeLineEvent.comment_id == event['comment_id']) \ .first() if not timeline_event: return None # Retrieve the story from the timeline event. story = db_api.entity_get(models.Story, timeline_event.story_id, session=session) if not story: return None # Construct and return the comment's event_info object. return { 'comment_id': comment.id, 'comment_content': comment.content, 'story_id': story.id, 'story_title': story.title }
def resolve_comments(self, session, event): # Sanity check. If there's no comment_id, exit. if 'comment_id' not in event: return None # Retrieve the content of the comment. comment = db_api.entity_get(models.Comment, event.comment_id, session=session) if not comment: return None # Retrieve the timeline events. timeline_event = session.query(models.TimeLineEvent) \ .filter(models.TimeLineEvent.comment_id == event.comment_id) \ .first() if not timeline_event: return None # Retrieve the story from the timeline event. story = db_api.entity_get(models.Story, timeline_event.story_id, session=session) if not story: return None # Construct and return the comment's event_info object. return { 'comment_id': comment.id, 'comment_content': comment.content, 'story_id': story.id, 'story_title': story.title }
def update_filter(filter_id, update): old_filter = api_base.entity_get(models.WorklistFilter, filter_id) if 'filter_criteria' in update: # Change the criteria for this filter. If an ID is provided, change # the criterion to match the provided criterion. If no ID is provided, # create a new criterion and add it to the filter. for criterion in update['filter_criteria']: criterion_dict = criterion.as_dict(omit_unset=True) if 'id' in criterion_dict: id = criterion_dict.pop('id') api_base.entity_update(models.FilterCriterion, id, criterion_dict) else: created = api_base.entity_create(models.FilterCriterion, criterion_dict) criterion.id = created old_filter.criteria.append(created) # Remove criteria which aren't in the provided set new_ids = [criterion.id for criterion in update['filter_criteria']] for criterion in old_filter.criteria: if criterion.id not in new_ids: old_filter.criteria.remove(criterion) del update['filter_criteria'] return api_base.entity_update(models.WorklistFilter, filter_id, update)
def test_handle(self): """Assert that the handle method passes the correct values on.""" worker_base = MockEmailWorkerBase({}) with base.HybridSessionManager(): session = db_api_base.get_session() user_1 = db_api_base.entity_get(models.User, 1, session=session) worker_base.handle(session=session, author=user_1, method='POST', url='http://localhost/', path='/test', query_string='', status=201, resource='story', resource_id=1) self.assertIsInstance(worker_base.handled_values['author'], models.User) self.assertEqual(1, worker_base.handled_values['author'].id) self.assertEqual(2, len(worker_base.handled_values['subscribers'])) self.assertEqual('POST', worker_base.handled_values['method']) self.assertEqual(201, worker_base.handled_values['status']) self.assertEqual('/test', worker_base.handled_values['path']) self.assertEqual('story', worker_base.handled_values['resource']) self.assertEqual(1, worker_base.handled_values['resource_id'])
def handle_timeline_events(self, session, resource, author, subscribers): for user_id in subscribers: user = db_api.entity_get(models.User, user_id, session=session) send_notification = get_preference( 'receive_notifications_worklists', user) if (send_notification != 'true' and resource.get('worklist_id') is not None): continue if resource['event_type'] == 'user_comment': event_info = json.dumps( self.resolve_comments(session=session, event=resource)) else: event_info = resource['event_info'] # Don't send a notification if the user isn't allowed to see the # thing this event is about. event = events_api.event_get(resource['id'], current_user=user_id, session=session) if not events_api.is_visible(event, user_id, session=session): continue db_api.entity_create(models.SubscriptionEvents, { "author_id": author.id, "subscriber_id": user_id, "event_type": resource['event_type'], "event_info": event_info }, session=session)
def user_get(user_id, filter_non_public=False, session=None): entity = api_base.entity_get(models.User, user_id, filter_non_public=filter_non_public, session=session) return entity
def handle_timeline_events(self, session, resource, author, subscribers): for user_id in subscribers: user = db_api.entity_get(models.User, user_id, session=session) send_notification = get_preference( 'receive_notifications_worklists', user) if (send_notification != 'true' and resource.get('worklist_id') is not None): continue if resource['event_type'] == 'user_comment': event_info = json.dumps( self.resolve_comments(session=session, event=resource) ) else: event_info = resource['event_info'] # Don't send a notification if the user isn't allowed to see the # thing this event is about. event = events_api.event_get( resource['id'], current_user=user_id, session=session) if not events_api.is_visible(event, user_id, session=session): continue db_api.entity_create(models.SubscriptionEvents, { "author_id": author.id, "subscriber_id": user_id, "event_type": resource['event_type'], "event_info": event_info }, session=session)
def comment_update(comment_id, values): comment = api_base.entity_get(models.Comment, comment_id) old_dict = { 'comment_id': comment_id, 'content': comment.content } api_base.entity_create(models.HistoricalComment, old_dict) return api_base.entity_update(models.Comment, comment_id, values)
def subscription_get_resource(target_type, target_id, current_user=None): if target_type not in SUPPORTED_TYPES: return None if target_type == 'story': return stories_api.story_get(target_id, current_user=current_user) elif target_type == 'task': return tasks_api.task_get(target_id, current_user=current_user) return api_base.entity_get(SUPPORTED_TYPES[target_type], target_id)
def _apply_pagination(self, model_cls, query, marker=None, limit=None): marker_entity = None if marker: marker_entity = api_base.entity_get(model_cls, marker, True) return api_base.paginate_query(query=query, model=model_cls, limit=limit, sort_key="id", marker=marker_entity)
def test_handle_email(self, get_smtp_client): """Make sure that events from the queue are sent as emails.""" dummy_smtp = mock.Mock(smtplib.SMTP) worker_base = SubscriptionEmailWorker({}) get_smtp_client.return_value.__enter__ = dummy_smtp with base.HybridSessionManager(): session = db_api_base.get_session() author = db_api_base.entity_get(models.User, 2, session=session) story = db_api_base.entity_get(models.Story, 1, session=session) story_dict = story.as_dict() story_after_dict = copy.copy(story_dict) story_after_dict['title'] = 'New Test Title' subscribers = worker_base.get_subscribers(session, 'story', 1) self.assertEqual(2, len(subscribers)) worker_base.handle_email(session=session, author=author, subscribers=subscribers, method='PUT', url='http://localhost/', path='/stories/1', query_string='', status=200, resource='story', resource_id=1, resource_before=story_dict, resource_after=story_after_dict) # There should be two subscribers, but only one should get an # email since the other is a digest receiver. subscribed_user = db_api_base.entity_get(models.User, 3, session=session) self.assertEqual(dummy_smtp.return_value.sendmail.call_count, 1) self.assertEqual( dummy_smtp.return_value.sendmail.call_args[1]['to_addrs'], subscribed_user.email)
def test_get_preference(self): """Assert that the get_preference method functions as expected.""" worker_base = MockEmailWorkerBase({}) with base.HybridSessionManager(): session = db_api_base.get_session() user_1 = db_api_base.entity_get(models.User, 1, session=session) foo_value = worker_base.get_preference('foo', user_1) self.assertEqual('bar', foo_value) no_value = worker_base.get_preference('no_value', user_1) self.assertIsNone(no_value)
def create_filter(worklist_id, filter_dict): criteria = filter_dict.pop('filter_criteria') filter_dict['list_id'] = worklist_id filter = api_base.entity_create(models.WorklistFilter, filter_dict) filter = api_base.entity_get(models.WorklistFilter, filter.id) filter.criteria = [] for criterion in criteria: criterion_dict = criterion.as_dict() criterion_dict['filter_id'] = filter.id filter.criteria.append( api_base.entity_create(models.FilterCriterion, criterion_dict)) return filter
def _apply_pagination(self, model_cls, query, marker=None, offset=None, limit=None): marker_entity = None if marker: marker_entity = api_base.entity_get(model_cls, marker, True) return api_base.paginate_query(query=query, model=model_cls, limit=limit, sort_key="id", marker=marker_entity, offset=offset)
def subscription_get_resource(target_type, target_id, current_user=None): if target_type not in SUPPORTED_TYPES: return None if target_type == 'story': return stories_api.story_get(target_id, current_user=current_user) elif target_type == 'task': return tasks_api.task_get(target_id, current_user=current_user) elif target_type == 'worklist': worklist = worklists_api.get(target_id) if worklists_api.visible(worklist, current_user): return worklist return None return api_base.entity_get(SUPPORTED_TYPES[target_type], target_id)
def update_filter(filter_id, update): old_filter = api_base.entity_get(models.WorklistFilter, filter_id) if 'filter_criteria' in update: new_ids = [criterion.id for criterion in update['filter_criteria']] for criterion in update['filter_criteria']: criterion_dict = criterion.as_dict(omit_unset=True) if 'id' in criterion_dict: existing = api_base.entity_get(models.FilterCriterion, criterion['id']) if existing.as_dict() != criterion_dict: api_base.entity_update(models.FilterCriterion, criterion_dict['id'], criterion_dict) else: created = api_base.entity_create(models.FilterCriterion, criterion_dict) old_filter.criteria.append(created) for criterion in old_filter.criteria: if criterion.id not in new_ids: old_filter.criteria.remove(criterion) del update['filter_criteria'] return api_base.entity_update(models.WorklistFilter, filter_id, update)
def get_original_resource(self, resource, resource_id): """Given a resource name and ID, will load that resource and map it to a JSON object. """ if not resource or not resource_id or resource not in class_mappings.keys(): return None model_class, wmodel_class = class_mappings[resource] entity = api_base.entity_get(model_class, resource_id) if entity: return tojson(wmodel_class, wmodel_class.from_db_model(entity)) else: # In the case of a DELETE, the entity will be returned as None return None
def get_original_resource(self, resource, resource_id): """Given a resource name and ID, will load that resource and map it to a JSON object. """ if not resource or not resource_id or resource not in \ class_mappings.keys(): return None model_class, wmodel_class = class_mappings[resource] entity = api_base.entity_get(model_class, resource_id) if entity: return tojson(wmodel_class, wmodel_class.from_db_model(entity)) else: # In the case of a DELETE, the entity will be returned as None return None
def _apply_pagination(self, model_cls, query, marker=None, offset=None, limit=None, sort_field='id', sort_dir='asc'): if not sort_field: sort_field = 'id' if not sort_dir: sort_dir = 'asc' marker_entity = None if marker: marker_entity = api_base.entity_get(model_cls, marker, True) return api_base.paginate_query(query=query, model=model_cls, limit=limit, sort_key=sort_field, marker=marker_entity, offset=offset, sort_dir=sort_dir)
def handle(self, session, author, method, url, path, query_string, status, resource, resource_id, sub_resource=None, sub_resource_id=None, resource_before=None, resource_after=None): """This worker handles API events and attempts to determine whether they correspond to user subscriptions. :param session: An event-specific SQLAlchemy session. :param author: The author's user record. :param method: The HTTP Method. :param url: The Referer header from the request. :param path: The full HTTP Path requested. :param query_string: The HTTP query string from the request. :param status: The returned HTTP Status of the response. :param resource: The resource type. :param resource_id: The ID of the resource. :param sub_resource: The subresource type. :param sub_resource_id: The ID of the subresource. :param resource_before: The resource state before this event occurred. :param resource_after: The resource state after this event occurred. """ if resource == 'timeline_event': event = db_api.entity_get(models.TimeLineEvent, resource_id, session=session) subscribers = sub_api.subscription_get_all_subscriber_ids( 'story', event.story_id, session=session) self.handle_timeline_events(session, event, author, subscribers) elif resource == 'project_group': subscribers = sub_api.subscription_get_all_subscriber_ids( resource, resource_id, session=session) self.handle_resources(session=session, method=method, resource_id=resource_id, sub_resource_id=sub_resource_id, author=author, subscribers=subscribers) if method == 'DELETE' and not (sub_resource_id or sub_resource): self.handle_deletions(session, resource, resource_id)
def delete_filter(filter_id): filter = api_base.entity_get(models.WorklistFilter, filter_id) for criterion in filter.criteria: api_base.entity_hard_delete(models.FilterCriterion, criterion.id) api_base.entity_hard_delete(models.WorklistFilter, filter_id)
def access_token_get(access_token_id, session=None): return api_base.entity_get(models.AccessToken, access_token_id, session=session)
def get_filter(filter_id): return api_base.entity_get(models.WorklistFilter, filter_id)
def subscription_events_get(subscription_event_id): return api_base.entity_get(models.SubscriptionEvents, subscription_event_id)
def milestone_get(milestone_id): return api_base.entity_get(models.Milestone, milestone_id)
def subscription_get_all_subscriber_ids(resource, resource_id, session=None): '''Test subscription discovery. The tested algorithm is as follows: If you're subscribed to a project_group, you will be notified about project_group, project, story, and task changes. If you are subscribed to a project, you will be notified about project, story, and task changes. If you are subscribed to a task, you will be notified about changes to that task. If you are subscribed to a story, you will be notified about changes to that story and its tasks. :param resource: The name of the resource. :param resource_id: The ID of the resource. :return: A list of user id's. ''' affected = { 'project_group': set(), 'project': set(), 'story': set(), 'task': set() } # If we accidentally pass a timeline_event, we're actually going to treat # it as a story. if resource == 'timeline_event': event = api_base.entity_get(TimeLineEvent, resource_id, session=session) if event: resource = 'story' resource_id = event.story_id else: return set() # Sanity check exit. if resource not in affected.keys(): return set() # Make sure the requested resource is going to be handled. affected[resource].add(resource_id) # Resolve either from story->task or from task->story, so the root # resource id remains pristine. if resource == 'story': # Get this story's tasks query = api_base.model_query(models.Task.id, session=session) \ .filter(models.Task.story_id.in_(affected['story'])) affected['task'] = affected['task'] \ .union(r for (r,) in query.all()) elif resource == 'task': # Get this tasks's story query = api_base.model_query(models.Task.story_id, session=session) \ .filter(models.Task.id == resource_id) affected['story'].add(query.first().story_id) # If there are tasks, there will also be projects. if affected['task']: # Get all the tasks's projects query = api_base.model_query(distinct(models.Task.project_id), session=session) \ .filter(models.Task.id.in_(affected['task'])) affected['project'] = affected['project'] \ .union(r for (r,) in query.all()) # If there are projects, there will also be project groups. if affected['project']: # Get all the projects' groups. query = api_base.model_query( distinct(models.project_group_mapping.c.project_group_id), session=session) \ .filter(models.project_group_mapping.c.project_id .in_(affected['project'])) affected['project_group'] = affected['project_group'] \ .union(r for (r,) in query.all()) # Load all subscribers. subscribers = set() for affected_type in affected: query = api_base.model_query(distinct( models.Subscription.user_id), session=session) \ .filter(models.Subscription.target_type == affected_type) \ .filter(models.Subscription.target_id.in_(affected[affected_type])) results = query.all() subscribers = subscribers.union(r for (r,) in results) return subscribers
def project_get(project_id): return api_base.entity_get(models.Project, project_id)
def resolve_resource_by_name(self, session, resource_name, resource_id): if resource_name not in class_mappings: return None klass = class_mappings[resource_name][0] return db_api.entity_get(klass, resource_id, session=session)
def write_bug(self, owner, assignee, priority, status, tags, bug, branches): """Writes the story, task, task history, and conversation. :param owner: The bug owner SQLAlchemy entity. :param tags: The tag SQLAlchemy entities. :param bug: The Launchpad Bug record. """ #Checks to make sure that the branch for the bug exists for branch in branches: if not self.check_branch(branch): print('No %s branch found for %s project. Creating one now.' % (branch, self.project.name)) db_api.entity_create(Branch, { 'name': branch, 'project_id': self.project.id }, session=self.session) if hasattr(bug, 'date_created'): created_at = bug.date_created else: created_at = None if hasattr(bug, 'date_last_updated'): updated_at = bug.date_last_updated else: updated_at = None # Extract the launchpad ID from the self link. # example url: https://api.launchpad.net/1.0/bugs/1057477 url_match = re.search("([0-9]+)$", str(bug.self_link)) if not url_match: print('ERROR: Unable to extract launchpad ID from %s.' % (bug.self_link,)) print('ERROR: Please file a ticket.') return launchpad_id = int(url_match.groups()[0]) # If the title is too long, prepend it to the description and # truncate it. title = bug.title description = bug.description if len(title) > 100: title = title[:97] + '...' description = bug.title + '\n\n' + description # Create priority tag tags.append(self.build_priority_tag(priority)) # Sanity check. story = { 'id': launchpad_id, 'description': description, 'created_at': created_at, 'creator': owner, 'is_bug': True, 'title': title, 'updated_at': updated_at, 'tags': tags } duplicate = db_api.entity_get(Story, launchpad_id, session=self.session) if not duplicate: print("Importing Story: %s" % (bug.self_link,)) story = db_api.entity_create(Story, story, session=self.session) else: print("Existing Story: %s" % (bug.self_link,)) story = duplicate # Duplicate check- launchpad import creates one task per story, # so if we already have a project task on this story, skip it. This # is to properly replay imports in the case where errors occurred # during import. existing_task = db_api.model_query(Task, session=self.session) \ .filter(Task.story_id == launchpad_id) \ .filter(Task.project_id == self.project.id) \ .first() if not existing_task: print("- Adding task in project %s" % (self.project.name,)) for branch in branches: task = db_api.entity_create(Task, { 'title': title, 'assignee_id': assignee.id if assignee else None, 'project_id': self.project.id, 'branch_id': self.get_branch(branch).id, 'story_id': launchpad_id, 'created_at': created_at, 'updated_at': updated_at, 'priority': priority, 'status': status }, session=self.session) else: print("- Existing task in %s" % (self.project.name,)) task = existing_task # Duplication Check - If this story already has a creation event, # we don't need to create a new one. Otherwise, create it manually so # we don't trigger event notifications. story_created_event = db_api \ .model_query(TimeLineEvent, session=self.session) \ .filter(TimeLineEvent.story_id == launchpad_id) \ .filter(TimeLineEvent.event_type == event_types.STORY_CREATED) \ .first() if not story_created_event: print("- Generating story creation event") db_api.entity_create(TimeLineEvent, { 'story_id': launchpad_id, 'author_id': owner.id, 'event_type': event_types.STORY_CREATED, 'created_at': created_at }, session=self.session) # Create the creation event for the task, but only if we just created # a new task. if not existing_task: print("- Generating task creation event") db_api.entity_create(TimeLineEvent, { 'story_id': launchpad_id, 'author_id': owner.id, 'event_type': event_types.TASK_CREATED, 'created_at': created_at, 'event_info': json.dumps({ 'task_id': task.id, 'task_title': title }) }, session=self.session) # Create the discussion, loading any existing comments first. current_count = db_api \ .model_query(TimeLineEvent, session=self.session) \ .filter(TimeLineEvent.story_id == launchpad_id) \ .filter(TimeLineEvent.event_type == event_types.USER_COMMENT) \ .count() desired_count = len(bug.messages) print("- %s of %s comments already imported." % (current_count, desired_count)) for i in range(current_count, desired_count): print('- Importing comment %s of %s' % (i + 1, desired_count)) message = bug.messages[i] message_created_at = message.date_created message_owner = self.write_user(message.owner) comment = db_api.entity_create(Comment, { 'content': message.content, 'created_at': message_created_at }, session=self.session) db_api.entity_create(TimeLineEvent, { 'story_id': launchpad_id, 'author_id': message_owner.id, 'event_type': event_types.USER_COMMENT, 'comment_id': comment.id, 'created_at': message_created_at }, session=self.session)
def subscription_get_resource(target_type, target_id): if target_type not in SUPPORTED_TYPES: return None return api_base.entity_get(SUPPORTED_TYPES[target_type], target_id)
def write_bug(self, owner, assignee, priority, status, tags, bug): """Writes the story, task, task history, and conversation. :param owner: The bug owner SQLAlchemy entity. :param tags: The tag SQLAlchemy entities. :param bug: The Launchpad Bug record. """ if hasattr(bug, 'date_created'): created_at = bug.date_created else: created_at = None if hasattr(bug, 'date_last_updated'): updated_at = bug.date_last_updated else: updated_at = None # Extract the launchpad ID from the self link. # example url: https://api.launchpad.net/1.0/bugs/1057477 url_match = re.search("([0-9]+)$", str(bug.self_link)) if not url_match: print 'ERROR: Unable to extract launchpad ID from %s.' \ % (bug.self_link,) print 'ERROR: Please file a ticket.' return launchpad_id = int(url_match.groups()[0]) # If the title is too long, prepend it to the description and # truncate it. title = bug.title description = bug.description if len(title) > 100: title = title[:97] + '...' description = bug.title + '\n\n' + description # Sanity check. story = { 'id': launchpad_id, 'description': description, 'created_at': created_at, 'creator': owner, 'is_bug': True, 'title': title, 'updated_at': updated_at, 'tags': tags } duplicate = db_api.entity_get(Story, launchpad_id, session=self.session) if not duplicate: print "Importing Story: %s" % (bug.self_link, ) story = db_api.entity_create(Story, story, session=self.session) else: print "Existing Story: %s" % (bug.self_link, ) story = duplicate # Duplicate check- launchpad import creates one task per story, # so if we already have a project task on this story, skip it. This # is to properly replay imports in the case where errors occurred # during import. existing_task = db_api.model_query(Task, session=self.session) \ .filter(Task.story_id == launchpad_id) \ .filter(Task.project_id == self.project.id) \ .first() if not existing_task: print "- Adding task in project %s" % (self.project.name, ) task = db_api.entity_create( Task, { 'title': title, 'assignee_id': assignee.id if assignee else None, 'project_id': self.project.id, 'story_id': launchpad_id, 'created_at': created_at, 'updated_at': updated_at, 'priority': priority, 'status': status }, session=self.session) else: print "- Existing task in %s" % (self.project.name, ) task = existing_task # Duplication Check - If this story already has a creation event, # we don't need to create a new one. Otherwise, create it manually so # we don't trigger event notifications. story_created_event = db_api \ .model_query(TimeLineEvent, session=self.session) \ .filter(TimeLineEvent.story_id == launchpad_id) \ .filter(TimeLineEvent.event_type == event_types.STORY_CREATED) \ .first() if not story_created_event: print "- Generating story creation event" db_api.entity_create(TimeLineEvent, { 'story_id': launchpad_id, 'author_id': owner.id, 'event_type': event_types.STORY_CREATED, 'created_at': created_at }, session=self.session) # Create the creation event for the task, but only if we just created # a new task. if not existing_task: print "- Generating task creation event" db_api.entity_create(TimeLineEvent, { 'story_id': launchpad_id, 'author_id': owner.id, 'event_type': event_types.TASK_CREATED, 'created_at': created_at, 'event_info': json.dumps({ 'task_id': task.id, 'task_title': title }) }, session=self.session) # Create the discussion, loading any existing comments first. current_count = db_api \ .model_query(TimeLineEvent, session=self.session) \ .filter(TimeLineEvent.story_id == launchpad_id) \ .filter(TimeLineEvent.event_type == event_types.USER_COMMENT) \ .count() desired_count = len(bug.messages) print "- %s of %s comments already imported." % (current_count, desired_count) for i in range(current_count, desired_count): print '- Importing comment %s of %s' % (i + 1, desired_count) message = bug.messages[i] message_created_at = message.date_created message_owner = self.write_user(message.owner) comment = db_api.entity_create(Comment, { 'content': message.content, 'created_at': message_created_at }, session=self.session) db_api.entity_create(TimeLineEvent, { 'story_id': launchpad_id, 'author_id': message_owner.id, 'event_type': event_types.USER_COMMENT, 'comment_id': comment.id, 'created_at': message_created_at }, session=self.session)
def branch_get(branch_id): return api_base.entity_get(models.Branch, branch_id)
def user_get(user_id, filter_non_public=False): entity = api_base.entity_get(models.User, user_id, filter_non_public=filter_non_public) return entity
def refresh_token_get(refresh_token_id, session=None): return api_base.entity_get(models.RefreshToken, refresh_token_id, session=session)
def event_get(event_id): return api_base.entity_get(models.TimeLineEvent, event_id)
def comment_get(comment_id): return api_base.entity_get(models.Comment, comment_id)
def subscription_get_all_subscriber_ids(resource, resource_id, session=None): '''Test subscription discovery. The tested algorithm is as follows: If you're subscribed to a project_group, you will be notified about project_group, project, story, and task changes. If you are subscribed to a project, you will be notified about project, story, and task changes. If you are subscribed to a task, you will be notified about changes to that task. If you are subscribed to a story, you will be notified about changes to that story and its tasks. :param resource: The name of the resource. :param resource_id: The ID of the resource. :return: A list of user id's. ''' affected = { 'project_group': set(), 'project': set(), 'story': set(), 'task': set() } # If we accidentally pass a timeline_event, we're actually going to treat # it as a story. if resource == 'timeline_event': event = api_base.entity_get(TimeLineEvent, resource_id, session=session) if event: resource = 'story' resource_id = event.story_id else: return set() # Sanity check exit. if resource not in affected.keys(): return set() # Make sure the requested resource is going to be handled. affected[resource].add(resource_id) users = None # Resolve either from story->task or from task->story, so the root # resource id remains pristine. if resource == 'story': # If the story is private, make a whitelist of users to notify. story = api_base.model_query(models.Story, session) \ .options(subqueryload(models.Story.permissions)) \ .filter_by(id=resource_id).first() if story.private: users = [user.id for user in story.permissions[0].users] # Get this story's tasks query = api_base.model_query(models.Task.id, session=session) \ .filter(models.Task.story_id.in_(affected['story'])) affected['task'] = affected['task'] \ .union(r for (r,) in query.all()) elif resource == 'task': # Get this tasks's story query = api_base.model_query(models.Task.story_id, session=session) \ .filter(models.Task.id == resource_id) affected['story'].add(query.first().story_id) story = api_base.model_query(models.Story, session) \ .options(subqueryload(models.Story.permissions)) \ .filter_by(id=query.first().story_id).first() if story.private: users = [user.id for user in story.permissions[0].users] # If there are tasks, there will also be projects. if affected['task']: # Get all the tasks's projects query = api_base.model_query(distinct(models.Task.project_id), session=session) \ .filter(models.Task.id.in_(affected['task'])) affected['project'] = affected['project'] \ .union(r for (r,) in query.all()) # If there are projects, there will also be project groups. if affected['project']: # Get all the projects' groups. query = api_base.model_query( distinct(models.project_group_mapping.c.project_group_id), session=session) \ .filter(models.project_group_mapping.c.project_id .in_(affected['project'])) affected['project_group'] = affected['project_group'] \ .union(r for (r,) in query.all()) # Load all subscribers. subscribers = set() for affected_type in affected: query = api_base.model_query(distinct( models.Subscription.user_id), session=session) \ .filter(models.Subscription.target_type == affected_type) \ .filter(models.Subscription.target_id.in_(affected[affected_type])) if users is not None: query = query.filter(models.Subscription.user_id.in_(users)) results = query.all() subscribers = subscribers.union(r for (r, ) in results) return subscribers
def subscription_get(subscription_id): return api_base.entity_get(models.Subscription, subscription_id)
def task_get(task_id): return api_base.entity_get(models.Task, task_id)