Repository: ckan/ckan

Issue:
Recreate removed stats functionality using Activity Streams
**CKAN version**
2.9.1

**Describe the bug**
When Revisions were deprecated, some dependent code in stats needed to be removed (#5215).  However, these stats are necessary for the effective maintenance of CKAN installations by sysadmins/org admins - specifically:
 - most_edited
 - new_packages_by_week
 - deleted_packages_by_week
 - num_packages_by_week
 - most_edited_by_week

**Steps to reproduce**
Go to `/stats` page.

**Expected behavior**
The detailed stats are restored by using activity streams.



Discussion:
@jqnatividad @smotornyuk , I am interested to work on this issue. In this issue I understand that stats functionality needs to be recreated and rendered to activity stream (https://github.com/ckan/ckan/blob/2.9/ckan/templates/snippets/activity_stream.html). Please confirm my understanding. Thankyou : )
No. Stats functionality just needs to be recreated. Just forget about activity streams. 

### stats functionality needs to be recreated SOMEHOW and rendered exactly as before(as much as possible)
@smotornyuk @jqnatividad , I have investigated the source code of stats (2.8 and 2.9). I am stuck at what to use in place of revisions. I am keeping this on hold from my end.
Use activities instead of revisions
@smotornyuk, Do I need to develop stats functionality here https://github.com/ckan/ckan/blob/master/ckan/views/dataset.py#L1127-L1166 and render it to  https://github.com/ckan/ckan/blob/master/ckanext/stats/templates/ckanext/stats/index.html? Please confirm my understanding. Thanks :)

Resolving patch:
diff --git a/ckanext/stats/blueprint.py b/ckanext/stats/blueprint.py
--- a/ckanext/stats/blueprint.py
+++ b/ckanext/stats/blueprint.py
@@ -1,8 +1,8 @@
 # encoding: utf-8
-
 from flask import Blueprint
 
 from ckan.plugins.toolkit import render
+import ckan.lib.helpers as h
 import ckanext.stats.stats as stats_lib
 
 
@@ -13,8 +13,41 @@
 def index():
     stats = stats_lib.Stats()
     extra_vars = {
-        u'largest_groups': stats.largest_groups(),
-        u'top_tags': stats.top_tags(),
-        u'top_package_creators': stats.top_package_creators(),
+        'largest_groups': stats.largest_groups(),
+        'top_tags': stats.top_tags(),
+        'top_package_creators': stats.top_package_creators(),
+        'most_edited_packages': stats.most_edited_packages(),
+        'new_packages_by_week': stats.get_by_week('new_packages'),
+        'deleted_packages_by_week': stats.get_by_week('deleted_packages'),
+        'num_packages_by_week': stats.get_num_packages_by_week(),
+        'package_revisions_by_week': stats.get_by_week('package_revisions')
     }
+
+    extra_vars['raw_packages_by_week'] = []
+    for week_date, num_packages, cumulative_num_packages\
+            in stats.get_num_packages_by_week():
+        extra_vars['raw_packages_by_week'].append(
+            {'date': h.date_str_to_datetime(week_date),
+             'total_packages': cumulative_num_packages})
+
+    extra_vars['raw_all_package_revisions'] = []
+    for week_date, revs, num_revisions, cumulative_num_revisions\
+            in stats.get_by_week('package_revisions'):
+        extra_vars['raw_all_package_revisions'].append(
+            {'date': h.date_str_to_datetime(week_date),
+             'total_revisions': num_revisions})
+
+    extra_vars['raw_new_datasets'] = []
+    for week_date, pkgs, num_packages, cumulative_num_revisions\
+            in stats.get_by_week('new_packages'):
+        extra_vars['raw_new_datasets'].append(
+            {'date': h.date_str_to_datetime(week_date),
+             'new_packages': num_packages})
+
+    extra_vars['raw_deleted_datasets'] = []
+    for week_date, pkgs, num_packages, cumulative_num_packages\
+            in stats.get_by_week('deleted_packages'):
+        extra_vars['raw_deleted_datasets'].append(
+            {'date': h.date_str_to_datetime(
+                week_date), 'deleted_packages': num_packages})
     return render(u'ckanext/stats/index.html', extra_vars)
diff --git a/ckanext/stats/plugin.py b/ckanext/stats/plugin.py
--- a/ckanext/stats/plugin.py
+++ b/ckanext/stats/plugin.py
@@ -14,10 +14,10 @@ class StatsPlugin(p.SingletonPlugin):
     p.implements(p.IConfigurer)
     p.implements(p.IBlueprint)
 
-    def get_blueprint(self):
-        return blueprint.stats
-
     def update_config(self, config):
         p.toolkit.add_template_directory(config, u'templates')
         p.toolkit.add_public_directory(config, u'public')
         p.toolkit.add_resource(u'public/ckanext/stats', u'ckanext_stats')
+
+    def get_blueprint(self):
+        return blueprint.stats
diff --git a/ckanext/stats/stats.py b/ckanext/stats/stats.py
--- a/ckanext/stats/stats.py
+++ b/ckanext/stats/stats.py
@@ -11,15 +11,15 @@
 
 log = logging.getLogger(__name__)
 cache_enabled = p.toolkit.asbool(
-    config.get('ckanext.stats.cache_enabled', False)
+    config.get("ckanext.stats.cache_enabled", False)
 )
 
 if cache_enabled:
     log.warn(
-        'ckanext.stats does not support caching in current implementations'
+        "ckanext.stats does not support caching in current implementations"
     )
 
-DATE_FORMAT = '%Y-%m-%d'
+DATE_FORMAT = "%Y-%m-%d"
 
 
 def table(name):
@@ -31,23 +31,28 @@ def datetime2date(datetime_):
 
 
 class Stats(object):
-
     @classmethod
     def largest_groups(cls, limit=10):
-        member = table('member')
-        package = table('package')
+        package = table("package")
+        activity = table("activity")
 
-        j = join(member, package, member.c.table_id == package.c.id)
+        j = join(activity, package, activity.c.object_id == package.c.id)
 
-        s = select(
-            [member.c.group_id,
-             func.count(member.c.table_id)]
-        ).select_from(j).group_by(member.c.group_id).where(
-            and_(
-                member.c.group_id != None, member.c.table_name == 'package',
-                package.c.private == False, package.c.state == 'active'
+        s = (
+            select([package.c.owner_org, func.count(package.c.id)])
+            .select_from(j)
+            .group_by(package.c.owner_org)
+            .where(
+                and_(
+                    package.c.owner_org != None,
+                    activity.c.activity_type == "new package",
+                    package.c.private == False,
+                    package.c.state == "active",
+                )
             )
-        ).order_by(func.count(member.c.table_id).desc()).limit(limit)
+            .order_by(func.count(package.c.id).desc())
+            .limit(limit)
+        )
 
         res_ids = model.Session.execute(s).fetchall()
         res_groups = [
@@ -57,12 +62,12 @@ def largest_groups(cls, limit=10):
         return res_groups
 
     @classmethod
-    def top_tags(cls, limit=10, returned_tag_info='object'):  # by package
-        assert returned_tag_info in ('name', 'id', 'object')
-        tag = table('tag')
-        package_tag = table('package_tag')
-        package = table('package')
-        if returned_tag_info == 'name':
+    def top_tags(cls, limit=10, returned_tag_info="object"):  # by package
+        assert returned_tag_info in ("name", "id", "object")
+        tag = table("tag")
+        package_tag = table("package_tag")
+        package = table("package")
+        if returned_tag_info == "name":
             from_obj = [package_tag.join(tag)]
             tag_column = tag.c.name
         else:
@@ -71,22 +76,29 @@ def top_tags(cls, limit=10, returned_tag_info='object'):  # by package
         j = join(
             package_tag, package, package_tag.c.package_id == package.c.id
         )
-        s = select([tag_column,
-                    func.count(package_tag.c.package_id)],
-                   from_obj=from_obj).select_from(j).where(
-                       and_(
-                           package_tag.c.state == 'active',
-                           package.c.private == False,
-                           package.c.state == 'active'
-                       )
-                   )
-        s = s.group_by(tag_column).order_by(
-            func.count(package_tag.c.package_id).desc()
-        ).limit(limit)
+        s = (
+            select(
+                [tag_column, func.count(package_tag.c.package_id)],
+                from_obj=from_obj,
+            )
+            .select_from(j)
+            .where(
+                and_(
+                    package_tag.c.state == "active",
+                    package.c.private == False,
+                    package.c.state == "active",
+                )
+            )
+        )
+        s = (
+            s.group_by(tag_column)
+            .order_by(func.count(package_tag.c.package_id).desc())
+            .limit(limit)
+        )
         res_col = model.Session.execute(s).fetchall()
-        if returned_tag_info in ('id', 'name'):
+        if returned_tag_info in ("id", "name"):
             return res_col
-        elif returned_tag_info == 'object':
+        elif returned_tag_info == "object":
             res_tags = [
                 (model.Session.query(model.Tag).get(text_type(tag_id)), val)
                 for tag_id, val in res_col
@@ -95,17 +107,329 @@ def top_tags(cls, limit=10, returned_tag_info='object'):  # by package
 
     @classmethod
     def top_package_creators(cls, limit=10):
-        userid_count = model.Session.query(
-            model.Package.creator_user_id,
-            func.count(model.Package.creator_user_id)
-        ).filter(model.Package.state == 'active'
-                 ).filter(model.Package.private == False).group_by(
-                     model.Package.creator_user_id
-                 ).order_by(func.count(model.Package.creator_user_id).desc()
-                            ).limit(limit).all()
+        userid_count = (
+            model.Session.query(
+                model.Package.creator_user_id,
+                func.count(model.Package.creator_user_id),
+            )
+            .filter(model.Package.state == "active")
+            .filter(model.Package.private == False)
+            .group_by(model.Package.creator_user_id)
+            .order_by(func.count(model.Package.creator_user_id).desc())
+            .limit(limit)
+            .all()
+        )
         user_count = [
             (model.Session.query(model.User).get(text_type(user_id)), count)
             for user_id, count in userid_count
             if user_id
         ]
         return user_count
+
+    @classmethod
+    def most_edited_packages(cls, limit=10):
+        package = table("package")
+        activity = table("activity")
+
+        s = (
+            select(
+                [package.c.id, func.count(activity.c.id)],
+                from_obj=[
+                    activity.join(
+                        package, activity.c.object_id == package.c.id
+                    )
+                ],
+            )
+            .where(
+                and_(
+                    package.c.private == False,
+                    activity.c.activity_type == "changed package",
+                    package.c.state == "active",
+                )
+            )
+            .group_by(package.c.id)
+            .order_by(func.count(activity.c.id).desc())
+            .limit(limit)
+        )
+        res_ids = model.Session.execute(s).fetchall()
+        res_pkgs = [
+            (model.Session.query(model.Package).get(text_type(pkg_id)), val)
+            for pkg_id, val in res_ids
+        ]
+        return res_pkgs
+
+    @classmethod
+    def get_package_revisions(cls):
+        """
+        @return: Returns list of revisions and date of them, in
+                 format: [(id, date), ...]
+        """
+        package = table("package")
+        activity = table("activity")
+        s = select(
+            [package.c.id, activity.c.timestamp],
+            from_obj=[
+                activity.join(package, activity.c.object_id == package.c.id)
+            ],
+        ).order_by(activity.c.timestamp)
+        res = model.Session.execute(s).fetchall()  # [(id, datetime), ...]
+        return res
+
+    @classmethod
+    def get_by_week(cls, object_type):
+        cls._object_type = object_type
+
+        def objects_by_week():
+            if cls._object_type == "new_packages":
+                objects = cls.get_new_packages()
+
+                def get_date(object_date):
+                    return datetime.date.fromordinal(object_date)
+
+            elif cls._object_type == "deleted_packages":
+                objects = cls.get_deleted_packages()
+
+                def get_date(object_date):
+                    return datetime.date.fromordinal(object_date)
+
+            elif cls._object_type == "package_revisions":
+                objects = cls.get_package_revisions()
+
+                def get_date(object_date):
+                    return datetime2date(object_date)
+
+            else:
+                raise NotImplementedError()
+            first_date = (
+                get_date(objects[0][1]) if objects else datetime.date.today()
+            )
+            week_commences = cls.get_date_week_started(first_date)
+            week_ends = week_commences + datetime.timedelta(days=7)
+            week_index = 0
+            weekly_pkg_ids = []  # [(week_commences, [pkg_id1, pkg_id2, ...])]
+            pkg_id_stack = []
+            cls._cumulative_num_pkgs = 0
+
+            def build_weekly_stats(week_commences, pkg_ids):
+                num_pkgs = len(pkg_ids)
+                cls._cumulative_num_pkgs += num_pkgs
+                return (
+                    week_commences.strftime(DATE_FORMAT),
+                    pkg_ids,
+                    num_pkgs,
+                    cls._cumulative_num_pkgs,
+                )
+
+            for pkg_id, date_field in objects:
+                date_ = get_date(date_field)
+                if date_ >= week_ends:
+                    weekly_pkg_ids.append(
+                        build_weekly_stats(week_commences, pkg_id_stack)
+                    )
+                    pkg_id_stack = []
+                    week_commences = week_ends
+                    week_ends = week_commences + datetime.timedelta(days=7)
+                pkg_id_stack.append(pkg_id)
+            weekly_pkg_ids.append(
+                build_weekly_stats(week_commences, pkg_id_stack)
+            )
+            today = datetime.date.today()
+            while week_ends <= today:
+                week_commences = week_ends
+                week_ends = week_commences + datetime.timedelta(days=7)
+                weekly_pkg_ids.append(build_weekly_stats(week_commences, []))
+            return weekly_pkg_ids
+
+        if cache_enabled:
+            log.warn(
+                "ckanext.stats does not support caching in current\
+                     implementations"
+            )
+        else:
+            objects_by_week_ = objects_by_week()
+        return objects_by_week_
+
+    @classmethod
+    def get_new_packages(cls):
+        """
+        @return: Returns list of new pkgs and date when they were created, in
+                 format: [(id, date_ordinal), ...]
+        """
+
+        def new_packages():
+            # Can't filter by time in select because 'min' function has to
+            # be 'for all time' else you get first revision in the time period.
+            package = table("package")
+            activity = table("activity")
+            s = (
+                select(
+                    [package.c.id, func.min(activity.c.timestamp)],
+                    from_obj=[
+                        activity.join(
+                            package, activity.c.object_id == package.c.id
+                        )
+                    ],
+                )
+                .group_by(package.c.id)
+                .order_by(func.min(activity.c.timestamp))
+            )
+            res = model.Session.execute(s).fetchall()  # [(id, datetime), ...]
+            res_pickleable = []
+            for pkg_id, created_datetime in res:
+                res_pickleable.append((pkg_id, created_datetime.toordinal()))
+            return res_pickleable
+
+        if cache_enabled:
+            log.warn(
+                "ckanext.stats does not support caching in current\
+                     implementations"
+            )
+        else:
+            new_packages = new_packages()
+        return new_packages
+
+    @classmethod
+    def get_date_week_started(cls, date_):
+        assert isinstance(date_, datetime.date)
+        if isinstance(date_, datetime.datetime):
+            date_ = datetime2date(date_)
+        return date_ - datetime.timedelta(days=datetime.date.weekday(date_))
+
+    @classmethod
+    def get_num_packages_by_week(cls):
+        def num_packages():
+            new_packages_by_week = cls.get_by_week("new_packages")
+            deleted_packages_by_week = cls.get_by_week("deleted_packages")
+            first_date = (
+                min(
+                    datetime.datetime.strptime(
+                        new_packages_by_week[0][0], DATE_FORMAT
+                    ),
+                    datetime.datetime.strptime(
+                        deleted_packages_by_week[0][0], DATE_FORMAT
+                    ),
+                )
+            ).date()
+            cls._cumulative_num_pkgs = 0
+            new_pkgs = []
+            deleted_pkgs = []
+
+            def build_weekly_stats(
+                week_commences, new_pkg_ids, deleted_pkg_ids
+            ):
+                num_pkgs = len(new_pkg_ids) - len(deleted_pkg_ids)
+                new_pkgs.extend(
+                    [
+                        model.Session.query(model.Package).get(id).name
+                        for id in new_pkg_ids
+                    ]
+                )
+                deleted_pkgs.extend(
+                    [
+                        model.Session.query(model.Package).get(id).name
+                        for id in deleted_pkg_ids
+                    ]
+                )
+                cls._cumulative_num_pkgs += num_pkgs
+                return (
+                    week_commences.strftime(DATE_FORMAT),
+                    num_pkgs,
+                    cls._cumulative_num_pkgs,
+                )
+
+            week_ends = first_date
+            today = datetime.date.today()
+            new_package_week_index = 0
+            deleted_package_week_index = 0
+            # [(week_commences, num_packages, cumulative_num_pkgs])]
+            weekly_numbers = []
+            while week_ends <= today:
+                week_commences = week_ends
+                week_ends = week_commences + datetime.timedelta(days=7)
+                if (
+                    datetime.datetime.strptime(
+                        new_packages_by_week[new_package_week_index][0],
+                        DATE_FORMAT,
+                    ).date()
+                    == week_commences
+                ):
+                    new_pkg_ids = new_packages_by_week[new_package_week_index][
+                        1
+                    ]
+                    new_package_week_index += 1
+                else:
+                    new_pkg_ids = []
+                if (
+                    datetime.datetime.strptime(
+                        deleted_packages_by_week[deleted_package_week_index][
+                            0
+                        ],
+                        DATE_FORMAT,
+                    ).date()
+                    == week_commences
+                ):
+                    deleted_pkg_ids = deleted_packages_by_week[
+                        deleted_package_week_index
+                    ][1]
+                    deleted_package_week_index += 1
+                else:
+                    deleted_pkg_ids = []
+                weekly_numbers.append(
+                    build_weekly_stats(
+                        week_commences, new_pkg_ids, deleted_pkg_ids
+                    )
+                )
+            # just check we got to the end of each count
+            assert new_package_week_index == len(new_packages_by_week)
+            assert deleted_package_week_index == len(deleted_packages_by_week)
+            return weekly_numbers
+
+        if cache_enabled:
+            log.warn(
+                "ckanext.stats does not support caching in current\
+                     implementations"
+            )
+        else:
+            num_packages = num_packages()
+        return num_packages
+
+    @classmethod
+    def get_deleted_packages(cls):
+        """
+        @return: Returns list of deleted pkgs and date when they were deleted,
+                 in format: [(id, date_ordinal), ...]
+        """
+
+        def deleted_packages():
+            # Can't filter by time in select because 'min' function has to
+            # be 'for all time' else you get first revision in the time period.
+            package = table("package")
+            activity = table("activity")
+
+            s = (
+                select(
+                    [package.c.id, func.min(activity.c.timestamp)],
+                    from_obj=[
+                        activity.join(
+                            package, activity.c.object_id == package.c.id
+                        )
+                    ],
+                )
+                .where(activity.c.activity_type == "deleted package")
+                .group_by(package.c.id)
+                .order_by(func.min(activity.c.timestamp))
+            )
+            res = model.Session.execute(s).fetchall()  # [(id, datetime), ...]
+            res_pickleable = []
+            for pkg_id, deleted_datetime in res:
+                res_pickleable.append((pkg_id, deleted_datetime.toordinal()))
+            return res_pickleable
+
+        if cache_enabled:
+            log.warn(
+                "ckanext.stats does not support caching in current\
+                     implementations"
+            )
+        else:
+            deleted_packages = deleted_packages()
+        return deleted_packages


Test patch:
diff --git a/ckanext/stats/tests/test_stats_lib.py b/ckanext/stats/tests/test_stats_lib.py
--- a/ckanext/stats/tests/test_stats_lib.py
+++ b/ckanext/stats/tests/test_stats_lib.py
@@ -1,35 +1,41 @@
 # encoding: utf-8
 
 import pytest
+import copy
+import datetime
 
 from ckan import model
 from ckan.tests import factories
 
+
 from ckanext.stats.stats import Stats
 
 
 @pytest.mark.ckan_config('ckan.plugins', 'stats')
 @pytest.mark.usefixtures("with_plugins", "with_request_context")
+@pytest.mark.freeze_time
 class TestStatsPlugin(object):
     @pytest.fixture(autouse=True)
-    def initial_data(self, clean_db, with_request_context):
+    def initial_data(self, clean_db, with_request_context, freezer):
+        # week 1
+        freezer.move_to('2011-1-5')
         user = factories.User(name="bob")
         org_users = [{"name": user["name"], "capacity": "editor"}]
         org1 = factories.Organization(name="org1", users=org_users)
         group2 = factories.Group()
         tag1 = {"name": "tag1"}
         tag2 = {"name": "tag2"}
-        factories.Dataset(
+        dataset1 = factories.Dataset(
             name="test1", owner_org=org1["id"], tags=[tag1], user=user
         )
-        factories.Dataset(
+        dataset2 = factories.Dataset(
             name="test2",
             owner_org=org1["id"],
             groups=[{"name": group2["name"]}],
             tags=[tag1],
             user=user,
         )
-        factories.Dataset(
+        dataset3 = factories.Dataset(
             name="test3",
             owner_org=org1["id"],
             groups=[{"name": group2["name"]}],
@@ -37,21 +43,66 @@ def initial_data(self, clean_db, with_request_context):
             user=user,
             private=True,
         )
-        factories.Dataset(name="test4", user=user)
+        dataset4 = factories.Dataset(name="test4", user=user)
 
         # week 2
-        model.Package.by_name(u"test2").delete()
+        freezer.move_to('2011-1-12')
+        model.Package.by_name(u'test2').delete()
+        activity2 = factories.Activity(
+            user_id=user["id"],
+            object_id=dataset2["id"],
+            activity_type="deleted package",
+            data={"package": copy.deepcopy(
+                dataset1), "actor": "Mr Someone"},
+        )
         model.repo.commit_and_remove()
 
         # week 3
-        model.Package.by_name(u"test3").title = "Test 3"
+        freezer.move_to('2011-1-19')
+        dataset3['title'] = "Test 3"
         model.repo.commit_and_remove()
-        model.Package.by_name(u"test4").title = "Test 4"
+        dataset1['title'] = 'Test 1'
+        factories.Activity(
+            user_id=user["id"],
+            object_id=dataset1["id"],
+            activity_type="changed package",
+            data={"package": copy.deepcopy(dataset1), "actor": "Mr Someone"},
+        )
+        freezer.move_to('2011-1-20')
+        model.repo.commit_and_remove()
+        dataset4['title'] = 'Test 4'
+        factories.Activity(
+            user_id=user["id"],
+            object_id=dataset4["id"],
+            activity_type="changed package",
+            data={"package": copy.deepcopy(dataset4), "actor": "Mr Someone"},
+        )
         model.repo.commit_and_remove()
 
         # week 4
-        model.Package.by_name(u"test3").notes = "Test 3 notes"
+        freezer.move_to('2011-1-26')
+        dataset3['notes'] = "Test 3 notes"
         model.repo.commit_and_remove()
+        dataset4['notes'] = 'test4 dataset'
+        factories.Activity(
+            user_id=user["id"],
+            object_id=dataset4["id"],
+            activity_type="changed package",
+            data={"package": copy.deepcopy(dataset4), "actor": "Mr Someone"},
+        )
+        model.repo.commit_and_remove()
+
+    def test_most_edited_packages(self):
+        pkgs = Stats.most_edited_packages()
+        pkgs = [(pkg.name, count) for pkg, count in pkgs]
+        # test2 does not come up because it was deleted
+        # test3 does not come up because it is private
+        test1 = 'test1', 1
+        test4 = 'test4', 2
+        assert len(pkgs[0]) == len(test1)
+        assert all([a == b for a, b in zip(pkgs[0], test4)])
+        assert len(pkgs[1]) == len(test4)
+        assert all([a == b for a, b in zip(pkgs[1], test1)])
 
     def test_largest_groups(self):
         grps = Stats.largest_groups()
@@ -73,3 +124,84 @@ def test_top_package_creators(self):
         # Only 2 shown because one of them was deleted and the other one is
         # private
         assert creators == [("bob", 2)]
+
+    def test_new_packages_by_week(self):
+        new_packages_by_week = Stats.get_by_week('new_packages')
+        # only 3 shown because one of them is private
+        # private packages are not shown in activity table
+        data1 = ('2011-01-03', set((u'test1', u'test2', u'test4')),
+                 3, 3)
+        data2 = ('2011-01-10', set([]), 0, 3)
+        data3 = ('2011-01-17', set([]), 0, 3)
+        data4 = ('2011-01-24', set([]), 0, 3)
+
+        def get_results(week_number):
+            date, ids, num, cumulative = new_packages_by_week[week_number]
+            return (date, set([model.Session.query(model.Package).get(id).name
+                               for id in ids]), num, cumulative)
+
+        assert len(get_results(0)) == len(data1)
+        assert all([a == b for a, b in zip(get_results(0), data1)])
+        assert len(get_results(1)) == len(data2)
+        assert all([a == b for a, b in zip(get_results(1), data2)])
+        assert len(get_results(2)) == len(data3)
+        assert all([a == b for a, b in zip(get_results(2), data3)])
+        assert len(get_results(3)) == len(data4)
+        assert all([a == b for a, b in zip(get_results(3), data4)])
+
+    def test_deleted_packages_by_week(self):
+        deleted_packages_by_week = Stats.get_by_week(
+            'deleted_packages')
+        data1 = ('2011-01-10', ['test2'], 1, 1)
+        data2 = ('2011-01-17', [], 0, 1)
+        data3 = ('2011-01-24', [], 0, 1)
+
+        def get_results(week_number):
+            date, ids, num, cumulative = deleted_packages_by_week[week_number]
+            return (date, [model.Session.query(model.Package).get(id).name for
+                           id in ids], num, cumulative)
+        assert len(get_results(0)) == len(data1)
+        assert all([a == b for a, b in zip(get_results(0), data1)])
+        assert len(get_results(1)) == len(data2)
+        assert all([a == b for a, b in zip(get_results(1), data2)])
+        assert len(get_results(2)) == len(data3)
+        assert all([a == b for a, b in zip(get_results(2), data3)])
+
+    def test_revisions_by_week(self):
+        revisions_by_week = Stats.get_by_week('package_revisions')
+
+        def get_results(week_number):
+            date, ids, num, cumulative = revisions_by_week[week_number]
+            return (date, num, cumulative)
+        num_setup_revs = revisions_by_week[0][2]
+        data1 = ('2011-01-03', num_setup_revs, num_setup_revs)
+        data2 = ('2011-01-10', 1, num_setup_revs + 1)
+        data3 = ('2011-01-17', 2, num_setup_revs + 3)
+        data4 = ('2011-01-24', 1, num_setup_revs + 4)
+        assert 6 > num_setup_revs > 2, num_setup_revs
+        assert len(get_results(0)) == len(data1)
+        assert all([a == b for a, b in zip(get_results(0), data1)])
+        assert len(get_results(1)) == len(data2)
+        assert all([a == b for a, b in zip(get_results(1), data2)])
+        assert len(get_results(2)) == len(data3)
+        assert all([a == b for a, b in zip(get_results(2), data3)])
+        assert len(get_results(3)) == len(data4)
+        assert all([a == b for a, b in zip(get_results(3), data4)])
+
+    def test_num_packages_by_week(self):
+        num_packages_by_week = Stats.get_num_packages_by_week()
+        # only 3 shown because one of them is private
+        # private packages are not shown in activity table
+        data1 = ('2011-01-03', 3, 3)
+        data2 = ('2011-01-10', -1, 2)
+        data3 = ('2011-01-17', 0, 2)
+        data4 = ('2011-01-24', 0, 2)
+        # e.g. [('2011-05-30', 3, 3)]
+        assert len(num_packages_by_week[0]) == len(data1)
+        assert all([a == b for a, b in zip(num_packages_by_week[0], data1)])
+        assert len(num_packages_by_week[1]) == len(data2)
+        assert all([a == b for a, b in zip(num_packages_by_week[1], data2)])
+        assert len(num_packages_by_week[2]) == len(data3)
+        assert all([a == b for a, b in zip(num_packages_by_week[2], data3)])
+        assert len(num_packages_by_week[3]) == len(data4)
+        assert all([a == b for a, b in zip(num_packages_by_week[3], data4)])