diff --git a/ckan/controllers/user.py b/ckan/controllers/user.py index 3976088886a..e32d32587cc 100644 --- a/ckan/controllers/user.py +++ b/ckan/controllers/user.py @@ -503,9 +503,9 @@ def dashboard(self, id=None): c.dashboard_activity_stream = h.dashboard_activity_stream(id) - # Mark the user's new activities as read whenever they view their + # Mark the user's new activities as old whenever they view their # dashboard page. - get_action('dashboard_mark_activities_as_read')(context, {}) + get_action('dashboard_mark_all_new_activities_as_old')(context, {}) return render('user/dashboard.html') diff --git a/ckan/lib/activity_streams.py b/ckan/lib/activity_streams.py index da3cac1f736..8ca1f193079 100644 --- a/ckan/lib/activity_streams.py +++ b/ckan/lib/activity_streams.py @@ -187,13 +187,9 @@ def activity_stream_string_new_related_item(): # A list of activity types that may have details activity_stream_actions_with_detail = ['changed package'] -def activity_list_to_html(context, activity_stream, is_dashboard=False): +def activity_list_to_html(context, activity_stream): '''Return the given activity stream as a snippet of HTML.''' - # get the last time they read the dashboard - if (is_dashboard): - last_viewed = logic.get_action('dashboard_get_last_viewed')(context, {}) - activity_list = [] # These are the activity stream messages. for activity in activity_stream: detail = None @@ -233,17 +229,12 @@ def activity_list_to_html(context, activity_stream, is_dashboard=False): for match in matches: snippet = activity_snippet_functions[match](activity, detail) data[str(match)] = snippet - timestamp = datetime.datetime.strptime(activity['timestamp'], '%Y-%m-%dT%H:%M:%S.%f').timetuple() - if (is_dashboard): - is_new = ( timestamp > last_viewed ) - else: - is_new = False; activity_list.append({'msg': activity_msg, 'type': activity_type.replace(' ', '-').lower(), 'icon': activity_icon, 'data': data, 'timestamp': activity['timestamp'], - 'is_new': is_new}) + 'is_new': activity.get('is_new', False)}) return literal(base.render('activity_streams/activity_stream_items.html', extra_vars={'activities': activity_list})) diff --git a/ckan/logic/action/get.py b/ckan/logic/action/get.py index 98d469ea7cb..95ab72119a6 100644 --- a/ckan/logic/action/get.py +++ b/ckan/logic/action/get.py @@ -1734,11 +1734,7 @@ def user_activity_list(context, data_dict): _check_access('user_show', context, data_dict) model = context['model'] user_id = _get_or_bust(data_dict, 'id') - query = model.Session.query(model.Activity) - query = query.filter_by(user_id=user_id) - query = query.order_by(_desc(model.Activity.timestamp)) - query = query.limit(15) - activity_objects = query.all() + activity_objects = model.activity.user_activity_list(user_id) return model_dictize.activity_list_dictize(activity_objects, context) def package_activity_list(context, data_dict): @@ -1757,11 +1753,7 @@ def package_activity_list(context, data_dict): _check_access('package_show', context, data_dict) model = context['model'] package_id = _get_or_bust(data_dict, 'id') - query = model.Session.query(model.Activity) - query = query.filter_by(object_id=package_id) - query = query.order_by(_desc(model.Activity.timestamp)) - query = query.limit(15) - activity_objects = query.all() + activity_objects = model.activity.package_activity_list(package_id) return model_dictize.activity_list_dictize(activity_objects, context) def group_activity_list(context, data_dict): @@ -1786,6 +1778,14 @@ def group_activity_list(context, data_dict): group_show = logic.get_action('group_show') group_id = group_show(context, {'id': group_id})['id'] + # FIXME: The SQLAlchemy below should be moved into ckan/model/activity.py + # (to encapsulate SQLALchemy in the model and avoid using it from the + # logic) but it can't be because it requires the list of dataset_ids which + # comes from logic.group_package_show() (and I don't want to access the + # logic from the model). Need to change it to get the dataset_ids from the + # model instead. There seems to be multiple methods for getting a group's + # datasets, some in the logic and some in the model. + # Get a list of the IDs of the group's datasets. group_package_show = logic.get_action('group_package_show') datasets = group_package_show(context, {'id': group_id}) @@ -1810,11 +1810,7 @@ def recently_changed_packages_activity_list(context, data_dict): # FIXME: Filter out activities whose subject or object the user is not # authorized to read. model = context['model'] - query = model.Session.query(model.Activity) - query = query.filter(model.Activity.activity_type.endswith('package')) - query = query.order_by(_desc(model.Activity.timestamp)) - query = query.limit(15) - activity_objects = query.all() + activity_objects = model.activity.recently_changed_packages_activity_list() return model_dictize.activity_list_dictize(activity_objects, context) def activity_detail_list(context, data_dict): @@ -2201,39 +2197,41 @@ def group_followee_list(context, data_dict): def dashboard_activity_list(context, data_dict): '''Return the authorized user's dashboard activity stream. - :rtype: list of dictionaries + Unlike the activity dictionaries returned by other *_activity_list actions, + these activity dictionaries have an extra boolean value with key 'is_new' + that tells you whether the activity happened since the user last viewed her + dashboard ('is_new': True) or not ('is_new': False). + + The user's own activities are always marked 'is_new': False. + + :rtype: list of activity dictionaries ''' - # FIXME: Filter out activities whose subject or object the user is not - # authorized to read. - if 'user' not in context: - raise logic.NotAuthorized( - _("You must be logged in to access your dashboard.")) + _check_access('dashboard_activity_list', context, data_dict) model = context['model'] + user_id = model.User.get(context['user']).id - userobj = model.User.get(context['user']) - if not userobj: - raise logic.NotAuthorized( - _("You must be logged in to access your dashboard.")) - user_id = userobj.id - - activity_query = model.Session.query(model.Activity) - user_followees_query = activity_query.join(model.UserFollowingUser, model.UserFollowingUser.object_id == model.Activity.user_id) - dataset_followees_query = activity_query.join(model.UserFollowingDataset, model.UserFollowingDataset.object_id == model.Activity.object_id) + # FIXME: Filter out activities whose subject or object the user is not + # authorized to read. + activity_objects = model.activity.dashboard_activity_list(user_id) - from_user_query = activity_query.filter(model.Activity.user_id==user_id) - about_user_query = activity_query.filter(model.Activity.object_id==user_id) - user_followees_query = user_followees_query.filter(model.UserFollowingUser.follower_id==user_id) - dataset_followees_query = dataset_followees_query.filter(model.UserFollowingDataset.follower_id==user_id) + activity_dicts = model_dictize.activity_list_dictize( + activity_objects, context) - query = from_user_query.union(about_user_query).union( - user_followees_query).union(dataset_followees_query) - query = query.order_by(_desc(model.Activity.timestamp)) - query = query.limit(15) - activity_objects = query.all() + # Mark the new (not yet seen by user) activities. + strptime = datetime.datetime.strptime + fmt = '%Y-%m-%dT%H:%M:%S.%f' + last_viewed = model.Dashboard.get_activity_stream_last_viewed(user_id) + for activity in activity_dicts: + if activity['user_id'] == user_id: + # Never mark the user's own activities as new. + activity['is_new'] = False + else: + activity['is_new'] = (strptime(activity['timestamp'], fmt) + > last_viewed) - return model_dictize.activity_list_dictize(activity_objects, context) + return activity_dicts def dashboard_activity_list_html(context, data_dict): @@ -2246,55 +2244,38 @@ def dashboard_activity_list_html(context, data_dict): ''' activity_stream = dashboard_activity_list(context, data_dict) - return activity_streams.activity_list_to_html(context, activity_stream, is_dashboard=True) + return activity_streams.activity_list_to_html(context, activity_stream) def dashboard_new_activities_count(context, data_dict): - '''Return the number of new activities in the user's activity stream. + '''Return the number of new activities in the user's dashboard. Return the number of new activities in the authorized user's dashboard activity stream. + Activities from the user herself are not counted by this function even + though they appear in the dashboard (users don't want to be notified about + things they did themselves). + :rtype: int ''' - # We don't bother to do our own auth check in this function, because we - # assume dashboard_activity_list will do it. - activities = dashboard_activity_list(context, data_dict) - - model = context['model'] - user = model.User.get(context['user']) # The authorized user. - last_viewed = model.Dashboard.get_activity_stream_last_viewed(user.id) - - strptime = datetime.datetime.strptime - fmt = '%Y-%m-%dT%H:%M:%S.%f' - new_activities = [activity for activity in activities if - strptime(activity['timestamp'], fmt) > last_viewed] - return len(new_activities) + _check_access('dashboard_new_activities_count', context, data_dict) + activities = logic.get_action('dashboard_activity_list')( + context, data_dict) + return len([activity for activity in activities if activity['is_new']]) -def dashboard_get_last_viewed(context, data_dict): - model = context['model'] - user = model.User.get(context['user']) # The authorized user. - last_viewed = model.Dashboard.get_activity_stream_last_viewed(user.id) - return last_viewed.timetuple() -def dashboard_mark_activities_as_read(context, data_dict): +def dashboard_mark_all_new_activities_as_old(context, data_dict): '''Mark all the authorized user's new dashboard activities as old. This will reset dashboard_new_activities_count to 0. ''' - if 'user' not in context: - raise logic.NotAuthorized( - _("You must be logged in to access your dashboard.")) - + _check_access('dashboard_mark_all_new_activities_as_old', context, + data_dict) model = context['model'] - - userobj = model.User.get(context['user']) - if not userobj: - raise logic.NotAuthorized( - _("You must be logged in to access your dashboard.")) - user_id = userobj.id + user_id = model.User.get(context['user']).id model.Dashboard.update_activity_stream_last_viewed(user_id) diff --git a/ckan/logic/auth/get.py b/ckan/logic/auth/get.py index 395bace3b15..cc3c20f88fe 100644 --- a/ckan/logic/auth/get.py +++ b/ckan/logic/auth/get.py @@ -189,3 +189,21 @@ def get_site_user(context, data_dict): return {'success': False, 'msg': 'Only internal services allowed to use this action'} else: return {'success': True} + + +def dashboard_activity_list(context, data_dict): + if 'user' in context: + return {'success': True} + else: + return {'success': False, + 'msg': _("You must be logged in to access your dashboard.")} + + +def dashboard_new_activities_count(context, data_dict): + return ckan.new_authz.is_authorized('dashboard_activity_list', + context, data_dict) + + +def dashboard_mark_all_new_activities_as_old(context, data_dict): + return ckan.new_authz.is_authorized('dashboard_activity_list', + context, data_dict) diff --git a/ckan/logic/auth/publisher/get.py b/ckan/logic/auth/publisher/get.py index 0e936c95105..41383de6a41 100644 --- a/ckan/logic/auth/publisher/get.py +++ b/ckan/logic/auth/publisher/get.py @@ -5,6 +5,11 @@ from ckan.logic.auth.publisher import _groups_intersect from ckan.authz import Authorizer from ckan.logic.auth import get_package_object, get_group_object, get_resource_object +from ckan.logic.auth.get import ( + dashboard_new_activities_count, + dashboard_activity_list, + dashboard_mark_all_new_activities_as_old, + ) def site_read(context, data_dict): """\ diff --git a/ckan/migration/versions/061_add_dashboard_table.py b/ckan/migration/versions/062_add_dashboard_table.py similarity index 100% rename from ckan/migration/versions/061_add_dashboard_table.py rename to ckan/migration/versions/062_add_dashboard_table.py diff --git a/ckan/model/activity.py b/ckan/model/activity.py index ff72337f04d..9181acaa20d 100644 --- a/ckan/model/activity.py +++ b/ckan/model/activity.py @@ -1,6 +1,6 @@ import datetime -from sqlalchemy import orm, types, Column, Table, ForeignKey +from sqlalchemy import orm, types, Column, Table, ForeignKey, desc import meta import types as _types @@ -48,6 +48,7 @@ def __init__(self, user_id, object_id, revision_id, activity_type, meta.mapper(Activity, activity_table) + class ActivityDetail(domain_object.DomainObject): def __init__(self, activity_id, object_id, object_type, activity_type, @@ -61,6 +62,139 @@ def __init__(self, activity_id, object_id, object_type, activity_type, else: self.data = data + meta.mapper(ActivityDetail, activity_detail_table, properties = { 'activity':orm.relation ( Activity, backref=orm.backref('activity_detail')) }) + + +def _most_recent_activities(q, limit): + import ckan.model as model + q = q.order_by(desc(model.Activity.timestamp)) + if limit: + q = q.limit(limit) + return q.all() + + +def _activities_from_user_query(user_id): + import ckan.model as model + q = model.Session.query(model.Activity) + q = q.filter(model.Activity.user_id == user_id) + return q + + +def _activities_about_user_query(user_id): + import ckan.model as model + q = model.Session.query(model.Activity) + q = q.filter(model.Activity.object_id == user_id) + return q + + +def _user_activity_query(user_id): + q = _activities_from_user_query(user_id) + q = q.union(_activities_about_user_query(user_id)) + return q + + +def user_activity_list(user_id, limit=15): + '''Return the given user's public activity stream. + + Returns all activities from or about the given user, i.e. where the given + user is the subject or object of the activity, e.g.: + + "{USER} created the dataset {DATASET}" + "{OTHER_USER} started following {USER}" + etc. + + ''' + q = _user_activity_query(user_id) + return _most_recent_activities(q, limit) + + +def _package_activity_query(package_id): + import ckan.model as model + q = model.Session.query(model.Activity) + q = q.filter_by(object_id=package_id) + return q + + +def package_activity_list(package_id, limit=15): + '''Return the given dataset (package)'s public activity stream. + + Returns all activities about the given dataset, i.e. where the given + dataset is the object of the activity, e.g.: + + "{USER} created the dataset {DATASET}" + "{USER} updated the dataset {DATASET}" + etc. + + ''' + q = _package_activity_query(package_id) + return _most_recent_activities(q, limit) + + +def _activites_from_users_followed_by_user_query(user_id): + import ckan.model as model + q = model.Session.query(model.Activity) + q = q.join(model.UserFollowingUser, + model.UserFollowingUser.object_id == model.Activity.user_id) + q = q.filter(model.UserFollowingUser.follower_id == user_id) + return q + + +def _activities_from_datasets_followed_by_user_query(user_id): + import ckan.model as model + q = model.Session.query(model.Activity) + q = q.join(model.UserFollowingDataset, + model.UserFollowingDataset.object_id == model.Activity.object_id) + q = q.filter(model.UserFollowingDataset.follower_id == user_id) + return q + + +def _activities_from_everything_followed_by_user_query(user_id): + q = _activites_from_users_followed_by_user_query(user_id) + q = q.union(_activities_from_datasets_followed_by_user_query(user_id)) + return q + + +def activities_from_everything_followed_by_user(user_id, limit=15): + '''Return activities from everything that the given user is following. + + Returns all activities where the object of the activity is anything + (user, dataset, group...) that the given user is following. + + ''' + q = _activities_from_everything_followed_by_user_query(user_id) + return _most_recent_activities(q, limit) + + +def _dashboard_activity_query(user_id): + q = _user_activity_query(user_id) + q = q.union(_activities_from_everything_followed_by_user_query(user_id)) + return q + + +def dashboard_activity_list(user_id, limit=15): + '''Return the given user's dashboard activity stream. + + Returns activities from the user's public activity stream, plus + activities from everything that the user is following. + + This is the union of user_activity_list(user_id) and + activities_from_everything_followed_by_user(user_id). + + ''' + q = _dashboard_activity_query(user_id) + return _most_recent_activities(q, limit) + + +def _recently_changed_packages_activity_query(): + import ckan.model as model + q = model.Session.query(model.Activity) + q = q.filter(model.Activity.activity_type.endswith('package')) + return q + + +def recently_changed_packages_activity_list(limit=15): + q = _recently_changed_packages_activity_query() + return _most_recent_activities(q, limit) diff --git a/ckan/public/base/less/iehacks.less b/ckan/public/base/less/iehacks.less index 6b7249c3e56..3d21b56ead5 100644 --- a/ckan/public/base/less/iehacks.less +++ b/ckan/public/base/less/iehacks.less @@ -68,6 +68,8 @@ .masthead .account a.image { display: block; width: 25px; + padding-right: 10px; + white-space: nowrap; } } diff --git a/ckan/tests/functional/api/test_activity.py b/ckan/tests/functional/api/test_activity.py index 6222ac48d02..4f7eff2a380 100644 --- a/ckan/tests/functional/api/test_activity.py +++ b/ckan/tests/functional/api/test_activity.py @@ -303,12 +303,14 @@ def check_dashboards(self, before, after, activity): new_activities = [activity_ for activity_ in after['user dashboard activity stream'] if activity_ not in before['user dashboard activity stream']] - assert new_activities == [activity] + assert [activity['id'] for activity in new_activities] == [ + activity['id']] new_activities = [activity_ for activity_ in after['follower dashboard activity stream'] if activity_ not in before['follower dashboard activity stream']] - assert new_activities == [activity] + assert [activity['id'] for activity in new_activities] == [ + activity['id']] def _create_package(self, user, name=None): if user: @@ -364,7 +366,8 @@ def _create_package(self, user, name=None): # There will be other new activities besides the 'follow dataset' one # because all the dataset's old activities appear in the user's # dashboard when she starts to follow the dataset. - assert activity in new_activities + assert activity['id'] in [ + activity['id'] for activity in new_activities] # The new activity should appear in the user "follower"'s dashboard # activity stream because she follows all the other users and datasets. @@ -374,7 +377,8 @@ def _create_package(self, user, name=None): # There will be other new activities besides the 'follow dataset' one # because all the dataset's old activities appear in the user's # dashboard when she starts to follow the dataset. - assert new_activities == [activity] + assert [activity['id'] for activity in new_activities] == [ + activity['id']] # The same new activity should appear on the dashboard's of the user's # followers. @@ -385,7 +389,8 @@ def _create_package(self, user, name=None): grp_new_activities = find_new_activities( before['group activity streams'][group_dict['name']], after['group activity streams'][group_dict['name']]) - assert grp_new_activities == [activity] + assert [activity['id'] for activity in grp_new_activities] == [ + activity['id']] # Check that the new activity has the right attributes. assert activity['object_id'] == package_created['id'], \ @@ -2122,7 +2127,8 @@ def test_follow_dataset(self): # There will be other new activities besides the 'follow dataset' one # because all the dataset's old activities appear in the user's # dashboard when she starts to follow the dataset. - assert activity in new_activities + assert activity['id'] in [ + activity['id'] for activity in new_activities] # The new activity should appear in the user "follower"'s dashboard # activity stream because she follows all the other users and datasets. @@ -2132,7 +2138,8 @@ def test_follow_dataset(self): # There will be other new activities besides the 'follow dataset' one # because all the dataset's old activities appear in the user's # dashboard when she starts to follow the dataset. - assert new_activities == [activity] + assert [activity['id'] for activity in new_activities] == [ + activity['id']] # Check that the new activity has the right attributes. assert activity['object_id'] == self.warandpeace['id'], \ @@ -2183,17 +2190,7 @@ def test_follow_user(self): assert len(user_new_activities) == 1, ("There should be 1 new " " activity in the user's activity stream, but found %i" % len(user_new_activities)) - assert user_new_activities[0] == activity - - # Check that the new activity appears in the followee's private - # activity stream. - followee_new_activities = (find_new_activities( - followee_before['follower dashboard activity stream'], - followee_after['follower dashboard activity stream'])) - assert len(followee_new_activities) == 1, ("There should be 1 new " - " activity in the user's activity stream, but found %i" % - len(followee_new_activities)) - assert followee_new_activities[0] == activity + assert user_new_activities[0]['id'] == activity['id'] # Check that the new activity has the right attributes. assert activity['object_id'] == self.sysadmin_user['id'], \ diff --git a/ckan/tests/functional/api/test_dashboard.py b/ckan/tests/functional/api/test_dashboard.py index 9cd330dd905..5c5cbd8443f 100644 --- a/ckan/tests/functional/api/test_dashboard.py +++ b/ckan/tests/functional/api/test_dashboard.py @@ -7,6 +7,20 @@ class TestDashboard(object): '''Tests for the logic action functions related to the user's dashboard.''' + @classmethod + def user_create(cls): + '''Create a new user.''' + params = json.dumps({ + 'name': 'mr_new_user', + 'email': 'mr@newuser.com', + 'password': 'iammrnew', + }) + response = cls.app.post('/api/action/user_create', params=params, + extra_environ={'Authorization': str(cls.joeadmin['apikey'])}) + assert response.json['success'] is True + new_user = response.json['result'] + return new_user + @classmethod def setup_class(cls): ckan.tests.CreateTestData.create() @@ -16,14 +30,24 @@ def setup_class(cls): 'id': joeadmin.id, 'apikey': joeadmin.apikey } + annafan = ckan.model.User.get('annafan') + cls.annafan = { + 'id': annafan.id, + 'apikey': annafan.apikey + } + testsysadmin = ckan.model.User.get('testsysadmin') + cls.testsysadmin = { + 'id': testsysadmin.id, + 'apikey': testsysadmin.apikey + } + cls.new_user = cls.user_create() @classmethod def teardown_class(cls): ckan.model.repo.rebuild_db() - def new_activities_count(self, user): + def dashboard_new_activities_count(self, user): '''Return the given user's new activities count from the CKAN API.''' - params = json.dumps({}) response = self.app.post('/api/action/dashboard_new_activities_count', params=params, @@ -32,56 +56,150 @@ def new_activities_count(self, user): new_activities_count = response.json['result'] return new_activities_count - def mark_as_read(self, user): + def dashboard_activity_list(self, user): + '''Return the given user's dashboard activity list from the CKAN API. + + ''' params = json.dumps({}) - response = self.app.post( - '/api/action/dashboard_mark_activities_as_read', + response = self.app.post('/api/action/dashboard_activity_list', params=params, extra_environ={'Authorization': str(user['apikey'])}) assert response.json['success'] is True + activity_list = response.json['result'] + return activity_list - def test_01_num_new_activities_new_user(self): - '''Test retreiving the number of new activities for a new user.''' + def dashboard_new_activities(self, user): + '''Return the activities from the user's dashboard activity stream + that are currently marked as new.''' + activity_list = self.dashboard_activity_list(user) + return [activity for activity in activity_list if activity['is_new']] - # Create a new user. - params = json.dumps({ - 'name': 'mr_new_user', - 'email': 'mr@newuser.com', - 'password': 'iammrnew', - }) - response = self.app.post('/api/action/user_create', params=params, - extra_environ={'Authorization': str(self.joeadmin['apikey'])}) + def dashboard_mark_all_new_activities_as_old(self, user): + params = json.dumps({}) + response = self.app.post( + '/api/action/dashboard_mark_all_new_activities_as_old', + params=params, + extra_environ={'Authorization': str(user['apikey'])}) assert response.json['success'] is True - new_user = response.json['result'] - - # We expect to find only one new activity for a newly registered user - # (A "{USER} signed up" activity). - assert self.new_activities_count(new_user) == 1 - - self.mark_as_read(new_user) - assert self.new_activities_count(new_user) == 0 - # Create a dataset. + def test_01_new_activities_count_for_new_user(self): + '''Test that a newly registered user's new activities count is 0.''' + assert self.dashboard_new_activities_count(self.new_user) == 0 + + def test_01_new_activities_for_new_user(self): + '''Test that a newly registered user has no activities marked as new + in their dashboard activity stream.''' + assert len(self.dashboard_new_activities(self.new_user)) == 0 + + def test_02_own_activities_do_not_count_as_new(self): + '''Make a user do some activities and check that her own activities + don't increase her new activities count.''' + + # The user has to view her dashboard activity stream first to mark any + # existing activities as read. For example when she follows a dataset + # below, past activities from the dataset (e.g. when someone created + # the dataset, etc.) will appear in her dashboard, and if she has never + # viewed her dashboard then those activities will be considered + # "unseen". + # We would have to do this if, when you follow something, you only get + # the activities from that object since you started following it, and + # not all its past activities as well. + self.dashboard_mark_all_new_activities_as_old(self.new_user) + + # Create a new dataset. params = json.dumps({ 'name': 'my_new_package', }) response = self.app.post('/api/action/package_create', params=params, - extra_environ={'Authorization': str(new_user['apikey'])}) + extra_environ={'Authorization': str(self.new_user['apikey'])}) assert response.json['success'] is True - # Now there should be a new 'user created dataset' activity. - assert self.new_activities_count(new_user) == 1 + # Follow a dataset. + params = json.dumps({'id': 'warandpeace'}) + response = self.app.post('/api/action/follow_dataset', params=params, + extra_environ={'Authorization': str(self.new_user['apikey'])}) + assert response.json['success'] is True - # Update the dataset. - params = json.dumps({ - 'name': 'my_new_package', - 'title': 'updated description', - }) + # Follow a user. + params = json.dumps({'id': 'annafan'}) + response = self.app.post('/api/action/follow_user', params=params, + extra_environ={'Authorization': str(self.new_user['apikey'])}) + assert response.json['success'] is True + + # Follow a group. + params = json.dumps({'id': 'roger'}) + response = self.app.post('/api/action/follow_group', params=params, + extra_environ={'Authorization': str(self.new_user['apikey'])}) + assert response.json['success'] is True + + # Update the dataset that we're following. + params = json.dumps({'name': 'warandpeace', 'notes': 'updated'}) + response = self.app.post('/api/action/package_update', params=params, + extra_environ={'Authorization': str(self.new_user['apikey'])}) + assert response.json['success'] is True + + # User's own actions should not increase her activity count. + assert self.dashboard_new_activities_count(self.new_user) == 0 + + def test_03_own_activities_not_marked_as_new(self): + '''Make a user do some activities and check that her own activities + aren't marked as new in her dashboard activity stream.''' + assert len(self.dashboard_new_activities(self.new_user)) == 0 + + def test_04_new_activities_count(self): + '''Test that new activities from objects that a user follows increase + her new activities count.''' + + # Make someone else who new_user is not following update a dataset that + # new_user is following. + params = json.dumps({'name': 'warandpeace', 'notes': 'updated again'}) response = self.app.post('/api/action/package_update', params=params, - extra_environ={'Authorization': str(new_user['apikey'])}) + extra_environ={'Authorization': str(self.joeadmin['apikey'])}) assert response.json['success'] is True - assert self.new_activities_count(new_user) == 2 + # Make someone that the user is following create a new dataset. + params = json.dumps({'name': 'annas_new_dataset'}) + response = self.app.post('/api/action/package_create', params=params, + extra_environ={'Authorization': str(self.annafan['apikey'])}) + assert response.json['success'] is True + + # Make someone that the user is not following update a dataset that + # the user is not following, but that belongs to a group that the user + # is following. + params = json.dumps({'name': 'annakarenina', 'notes': 'updated'}) + response = self.app.post('/api/action/package_update', params=params, + extra_environ={'Authorization': str(self.testsysadmin['apikey'])}) + assert response.json['success'] is True - self.mark_as_read(new_user) - assert self.new_activities_count(new_user) == 0 + # FIXME: The number here should be 3 but activities from followed + # groups are not appearing in dashboard. When that is fixed, fix this + # number. + assert self.dashboard_new_activities_count(self.new_user) == 2 + + def test_05_activities_marked_as_new(self): + '''Test that new activities from objects that a user follows are + marked as new in her dashboard activity stream.''' + # FIXME: The number here should be 3 but activities from followed + # groups are not appearing in dashboard. When that is fixed, fix this + # number. + assert len(self.dashboard_new_activities(self.new_user)) == 2 + + def test_06_mark_new_activities_as_read(self): + '''Test that a user's new activities are marked as old when she views + her dashboard activity stream.''' + assert self.dashboard_new_activities_count(self.new_user) > 0 + assert len(self.dashboard_new_activities(self.new_user)) > 0 + self.dashboard_mark_all_new_activities_as_old(self.new_user) + assert self.dashboard_new_activities_count(self.new_user) == 0 + assert len(self.dashboard_new_activities(self.new_user)) == 0 + + def test_07_maximum_number_of_new_activities(self): + '''Test that the new activities count does not go higher than 15, even + if there are more than 15 new activities from the user's followers.''' + for n in range(0,20): + notes = "Updated {n} times".format(n=n) + params = json.dumps({'name': 'warandpeace', 'notes': notes}) + response = self.app.post('/api/action/package_update', params=params, + extra_environ={'Authorization': str(self.joeadmin['apikey'])}) + assert response.json['success'] is True + assert self.dashboard_new_activities_count(self.new_user) == 15