
The pinned versions of webob and hacking weren't functioning properly with modern Python, so they are unpinned. Unpinning hacking brought a number of extra rules into use, so this commit also contains a number of code style fixes. There was also an incompatibility in the test_setup.sh script, which meant it doesn't work with MySQL 8.0 (which is in Ubuntu 20.04). This is also fixed in this commit. Change-Id: I0889bc35f8babfeec42f6f577d302bb5fbce1c95
206 lines
8.2 KiB
Python
206 lines
8.2 KiB
Python
# 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.
|
|
|
|
import json
|
|
|
|
import storyboard.db.api.base as db_api
|
|
from storyboard.db.api import subscriptions as sub_api
|
|
from storyboard.db.api import timeline_events as events_api
|
|
import storyboard.db.models as models
|
|
from storyboard.plugin.event_worker import WorkerTaskBase
|
|
|
|
|
|
def get_preference(name, user):
|
|
if name not in user.preferences:
|
|
return None
|
|
return user.preferences[name].cast_value
|
|
|
|
|
|
class Subscription(WorkerTaskBase):
|
|
def enabled(self):
|
|
"""This plugin is always enabled.
|
|
|
|
:return: True
|
|
"""
|
|
return True
|
|
|
|
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':
|
|
story_id = resource_after.get('story_id')
|
|
worklist_id = resource_after.get('worklist_id')
|
|
if story_id is not None:
|
|
subscribers = sub_api.subscription_get_all_subscriber_ids(
|
|
'story', story_id, session=session)
|
|
self.handle_timeline_events(
|
|
session, resource_after, author, subscribers)
|
|
if worklist_id is not None:
|
|
subscribers = sub_api.subscription_get_all_subscriber_ids(
|
|
'worklist', worklist_id, session=session)
|
|
self.handle_timeline_events(
|
|
session, resource_after, 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 handle_deletions(self, session, resource_name, resource_id):
|
|
target_subs = []
|
|
sub_ids = set()
|
|
resource_name = resource_name[:-1]
|
|
|
|
target_sub = db_api.entity_get_all(models.Subscription,
|
|
target_type=resource_name,
|
|
target_id=resource_id,
|
|
session=session)
|
|
target_subs.extend(target_sub)
|
|
|
|
for sub in target_subs:
|
|
sub_ids.add(sub.id)
|
|
|
|
for sub_id in sub_ids:
|
|
db_api.entity_hard_delete(models.Subscription,
|
|
sub_id,
|
|
session=session)
|
|
|
|
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 handle_resources(self, session, method, resource_id, sub_resource_id,
|
|
author, subscribers):
|
|
|
|
if sub_resource_id:
|
|
|
|
for user_id in subscribers:
|
|
|
|
if method == 'DELETE':
|
|
event_type = 'project removed from project_group'
|
|
event_info = json.dumps({'project_group_id': resource_id,
|
|
'project_id': sub_resource_id})
|
|
|
|
else:
|
|
event_type = 'project added to project_group'
|
|
event_info = json.dumps({'project_group_id': resource_id,
|
|
'project_id': sub_resource_id})
|
|
|
|
db_api.entity_create(models.SubscriptionEvents, {
|
|
"author_id": author.id,
|
|
"subscriber_id": user_id,
|
|
"event_type": event_type,
|
|
"event_info": event_info
|
|
}, session=session)
|
|
|
|
else:
|
|
if method == 'DELETE':
|
|
# Handling project_group targeted.
|
|
for user_id in subscribers:
|
|
db_api.entity_create(models.SubscriptionEvents, {
|
|
"author_id": author.id,
|
|
"subscriber_id": user_id,
|
|
"event_type": 'project_group deleted',
|
|
"event_info": json.dumps(
|
|
{'project_group_id': resource_id})
|
|
}, session=session)
|
|
|
|
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
|
|
}
|