Created Subscriber Utility

A utility which automatically returns a list of all users who
are subscribed to a given resource. Relevant use cases have
been updated.

Change-Id: I55cc011639af6d67ee6c809166dd070c82b59eef
This commit is contained in:
Michael Krotscheck 2015-01-13 10:54:01 -08:00
parent 2315f0ba9c
commit 09ea0eac51
5 changed files with 174 additions and 80 deletions

View File

@ -13,6 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from sqlalchemy import distinct
from storyboard.db.api import base as api_base
from storyboard.db import models
@ -59,3 +61,86 @@ def subscription_delete(subscription_id):
if subscription:
api_base.entity_hard_delete(models.Subscription, subscription_id)
def subscription_get_all_subscriber_ids(resource, resource_id):
'''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()
}
# 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) \
.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) \
.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)) \
.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)) \
.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)) \
.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

View File

@ -16,57 +16,15 @@
import json
from storyboard.db.api import comments as comments_api
from storyboard.db.api import project_groups as project_groups_api
from storyboard.db.api import stories as story_api
from storyboard.db.api import subscription_events as sub_events_api
from storyboard.db.api import subscriptions as subscriptions_api
from storyboard.db.api import tasks as tasks_api
from storyboard.db.api import timeline_events as timeline_api
def handle_timeline_events(event, author_id):
def handle_timeline_events(event, author_id, subscribers):
target_subs = []
user_ids = set()
story_id = event.story_id
if event.event_info:
event_info = json.loads(event.event_info)
task_id = event_info.get("task_id")
# Handling tasks targeted.
target_sub = subscriptions_api.subscription_get_all_by_target(
['task'], task_id)
target_subs.extend(target_sub)
# Handling stories targeted.
target_sub = subscriptions_api.subscription_get_all_by_target(
['story'], story_id)
target_subs.extend(target_sub)
# Handling projects, project groups targeted for stories without tasks.
tasks = tasks_api.task_get_all(story_id=story_id)
for task in tasks:
project_id = task.project_id
# Handling projects targeted.
target_sub = subscriptions_api.subscription_get_all_by_target(
['project'], project_id)
target_subs.extend(target_sub)
# Handling project groups targeted.
pgs = project_groups_api.project_group_get_all(project_id=project_id)
for pg in pgs:
target_sub = subscriptions_api.subscription_get_all_by_target(
['project_group'], pg.id)
target_subs.extend(target_sub)
for sub in target_subs:
user_ids.add(sub.user_id)
for user_id in user_ids:
for user_id in subscribers:
if event.event_type == 'user_comment':
event_info = resolve_comments(event)
@ -81,22 +39,12 @@ def handle_timeline_events(event, author_id):
})
def handle_resources(method, resource_id, sub_resource_id, author_id):
target_subs = []
user_ids = set()
def handle_resources(method, resource_id, sub_resource_id, author_id,
subscribers):
if sub_resource_id:
# Handling project addition/deletion to/from project_group.
target_sub = subscriptions_api.subscription_get_all_by_target(
['project'], sub_resource_id)
target_subs.extend(target_sub)
for sub in target_subs:
user_ids.add(sub.user_id)
for user_id in user_ids:
for user_id in subscribers:
if method == 'DELETE':
event_type = 'project removed from project_group'
@ -118,14 +66,7 @@ def handle_resources(method, resource_id, sub_resource_id, author_id):
else:
if method == 'DELETE':
# Handling project_group targeted.
target_sub = subscriptions_api.subscription_get_all_by_target(
['project_group'], resource_id)
target_subs.extend(target_sub)
for sub in target_subs:
user_ids.add(sub.user_id)
for user_id in user_ids:
for user_id in subscribers:
sub_events_api.subscription_events_create({
"author_id": author_id,
"subscriber_id": user_id,
@ -135,7 +76,6 @@ def handle_resources(method, resource_id, sub_resource_id, author_id):
def handle_deletions(resource_name, resource_id):
target_subs = []
sub_ids = set()
resource_name = resource_name[:-1]

View File

@ -0,0 +1,69 @@
# Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from storyboard.db.api.subscriptions import subscription_get_all_subscriber_ids
from storyboard.tests import base
class TestSubscription(base.FunctionalTest):
'''Tests for the subscription db interface.
'''
def test_get_subscribers(self):
'''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.
'''
subscribers = subscription_get_all_subscriber_ids('invalid', 1)
self.assertSetEqual(set(), subscribers)
subscribers = subscription_get_all_subscriber_ids('story', 1)
self.assertSetEqual({1, 3}, subscribers)
subscribers = subscription_get_all_subscriber_ids('story', 2)
self.assertSetEqual(set(), subscribers)
subscribers = subscription_get_all_subscriber_ids('story', 3)
self.assertSetEqual(set(), subscribers)
subscribers = subscription_get_all_subscriber_ids('story', 4)
self.assertSetEqual(set(), subscribers)
subscribers = subscription_get_all_subscriber_ids('story', 5)
self.assertSetEqual(set(), subscribers)
subscribers = subscription_get_all_subscriber_ids('task', 1)
self.assertSetEqual({1, 3}, subscribers)
subscribers = subscription_get_all_subscriber_ids('task', 2)
self.assertSetEqual({3}, subscribers)
subscribers = subscription_get_all_subscriber_ids('task', 3)
self.assertSetEqual({1, 3}, subscribers)
subscribers = subscription_get_all_subscriber_ids('task', 4)
self.assertSetEqual(set(), subscribers)
subscribers = subscription_get_all_subscriber_ids('project', 1)
self.assertSetEqual({1}, subscribers)
subscribers = subscription_get_all_subscriber_ids('project', 2)
self.assertSetEqual(set(), subscribers)
subscribers = subscription_get_all_subscriber_ids('project', 3)
self.assertSetEqual({1}, subscribers)
subscribers = subscription_get_all_subscriber_ids('project_group', 1)
self.assertSetEqual({1}, subscribers)
subscribers = subscription_get_all_subscriber_ids('project_group', 2)
self.assertSetEqual(set(), subscribers)
subscribers = subscription_get_all_subscriber_ids('project_group', 3)
self.assertSetEqual(set(), subscribers)

View File

@ -196,7 +196,7 @@ def load():
Subscription(
id=1,
user_id=1,
target_type='project',
target_type='project_group',
target_id=1
),
Subscription(

View File

@ -14,6 +14,7 @@
import json
from storyboard.db.api import subscriptions
from storyboard.db.api import timeline_events
from storyboard.notifications.subscriptions_handler import handle_deletions
from storyboard.notifications.subscriptions_handler import handle_resources
@ -31,23 +32,22 @@ class Subscription(WorkerTaskBase):
:return:
"""
body_dict = json.loads(body)
subscribers = subscriptions.subscription_get_all_subscriber_ids(
body_dict['resource'], body_dict['resource_id']
)
if 'event_id' in body_dict:
event_id = body_dict['event_id']
event = timeline_events.event_get(event_id)
handle_timeline_events(event, body_dict['author_id'])
handle_timeline_events(event, body_dict['author_id'], subscribers)
else:
if body_dict['resource'] == 'project_groups':
if 'sub_resource_id' in body_dict:
handle_resources(method=body_dict['method'],
resource_id=body_dict['resource_id'],
sub_resource_id=body_dict[
'sub_resource_id'],
author_id=body_dict['author_id'])
else:
handle_resources(method=body_dict['method'],
resource_id=body_dict['resource_id'],
author_id=body_dict['author_id'])
elif body_dict['resource'] == 'project_groups':
handle_resources(method=body_dict['method'],
resource_id=body_dict['resource_id'],
sub_resource_id=getattr(body_dict,
'sub_resource_id', None),
author_id=body_dict['author_id'],
subscribers=subscribers)
if body_dict['method'] == 'DELETE':
resource_name = body_dict['resource']