allura-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From kentontay...@apache.org
Subject allura git commit: [#8198] option to delete activity records
Date Mon, 23 Apr 2018 22:04:07 GMT
Repository: allura
Updated Branches:
  refs/heads/master 2445c75e1 -> 911d1bac4


[#8198] option to delete activity records


Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/911d1bac
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/911d1bac
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/911d1bac

Branch: refs/heads/master
Commit: 911d1bac447027c9bfcc7f5005e778cefd986d97
Parents: 2445c75
Author: Dave Brondsema <dave@brondsema.net>
Authored: Fri Mar 30 17:33:09 2018 -0400
Committer: Kenton Taylor <ktaylor@slashdotmedia.com>
Committed: Mon Apr 23 21:16:05 2018 +0000

----------------------------------------------------------------------
 ForgeActivity/forgeactivity/main.py             | 31 +++++++++
 .../forgeactivity/nf/activity/css/activity.css  |  3 +
 .../forgeactivity/nf/activity/js/activity.js    | 35 ++++++++++
 .../forgeactivity/templates/timeline.html       |  2 +-
 .../forgeactivity/tests/functional/test_root.py | 71 +++++++++++++++++++-
 requirements.txt                                |  2 +-
 6 files changed, 140 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/allura/blob/911d1bac/ForgeActivity/forgeactivity/main.py
----------------------------------------------------------------------
diff --git a/ForgeActivity/forgeactivity/main.py b/ForgeActivity/forgeactivity/main.py
index 6875d8b..0786db6 100644
--- a/ForgeActivity/forgeactivity/main.py
+++ b/ForgeActivity/forgeactivity/main.py
@@ -17,8 +17,10 @@
 
 import logging
 import calendar
+from datetime import timedelta
 from itertools import islice, ifilter
 
+from bson import ObjectId
 from ming.orm import session
 from pylons import tmpl_context as c, app_globals as g
 from pylons import request, response
@@ -27,6 +29,7 @@ from tg.decorators import with_trailing_slash, without_trailing_slash
 from paste.deploy.converters import asbool, asint
 from webob import exc
 from webhelpers import feedgenerator as FG
+from activitystream.storage.mingstorage import Activity
 
 from allura.app import Application
 from allura import version
@@ -208,6 +211,34 @@ class ForgeActivityController(BaseController):
             message=W.follow_toggle.success_message(follow),
             following=follow)
 
+    @require_post()
+    @expose('json:')
+    def delete_item(self, activity_id, **kwargs):
+        require_access(c.project.neighborhood, 'admin')
+        activity = Activity.query.get(_id=ObjectId(activity_id))
+        if not activity:
+            raise exc.HTTPGone
+        # find other copies of this activity on other user/projects timelines
+        # but only within a small time window, so we can do efficient searching
+        activity_ts = activity._id.generation_time
+        time_window = timedelta(hours=1)
+        all_copies = Activity.query.find({
+            '_id': {
+                '$gt': ObjectId.from_datetime(activity_ts - time_window),
+                '$lt': ObjectId.from_datetime(activity_ts + time_window),
+            },
+            'obj': activity.obj,
+            'target': activity.target,
+            'actor': activity.actor,
+            'verb': activity.verb,
+            'tags': activity.tags,
+        }).all()
+        log.info('Deleting %s copies of activity record: %s %s %s', len(all_copies),
+                 activity.actor.activity_url, activity.verb, activity.obj.activity_url)
+        for activity in all_copies:
+            activity.query.delete()
+        return {'success': True}
+
 
 class ForgeActivityRestController(BaseController, AppRestControllerMixin):
 

http://git-wip-us.apache.org/repos/asf/allura/blob/911d1bac/ForgeActivity/forgeactivity/nf/activity/css/activity.css
----------------------------------------------------------------------
diff --git a/ForgeActivity/forgeactivity/nf/activity/css/activity.css b/ForgeActivity/forgeactivity/nf/activity/css/activity.css
index 55551de..f039ca9 100644
--- a/ForgeActivity/forgeactivity/nf/activity/css/activity.css
+++ b/ForgeActivity/forgeactivity/nf/activity/css/activity.css
@@ -49,6 +49,9 @@
   float: left;
   margin: 10px 10px 0 0;
 }
+.activity ul.timeline li input[name=delete] {
+  float: right;
+}
 .activity .page_list {
     margin-top: 5px;
 }

http://git-wip-us.apache.org/repos/asf/allura/blob/911d1bac/ForgeActivity/forgeactivity/nf/activity/js/activity.js
----------------------------------------------------------------------
diff --git a/ForgeActivity/forgeactivity/nf/activity/js/activity.js b/ForgeActivity/forgeactivity/nf/activity/js/activity.js
index ad835d2..b0c83e7 100644
--- a/ForgeActivity/forgeactivity/nf/activity/js/activity.js
+++ b/ForgeActivity/forgeactivity/nf/activity/js/activity.js
@@ -282,9 +282,44 @@ $(function() {
         }
     }
 
+    function enableDeleteButtons() {
+        var confirmed = false;
+        $('.timeline').on('mouseenter', 'li[data-can-delete]', function() {
+            $(this).prepend('<input type=button value=Delete name=delete title="Permanently
deletes this item from all users/projects activity records.<br>Only neighborhood admins
can do this.">');
+            $('input[name=delete]', this).tooltipster({contentAsHTML: true});
+        });
+        $('.timeline').on('mouseleave', 'li[data-can-delete]', function() {
+            $('input[name=delete]', this).remove();
+        });
+        $('.timeline').on('click', 'li[data-can-delete] input[name=delete]', function() {
+            if (!confirmed) {
+                confirmed = confirm('Are you sure you want to delete this?  You cannot undo!');
+                if (!confirmed) {
+                    return;
+                }
+            }
+            $(this).prop('disabled', true);
+            $(this).val('Deleting...')
+            var $row = $(this).closest('[data-can-delete]');
+            $row.css('background', 'lightgray');
+            $.post('delete_item', {
+                activity_id: $row.attr('id'),
+                _session_id: $.cookie('_session_id')
+            }).done(function() {
+                $('input[name=delete]', $row).remove();
+                $row.css('text-decoration', 'line-through').removeAttr('data-can-delete');
+            }).fail(function() {
+                flash('Deleting failed.', 'error');
+                $row.css('background', 'orange');
+            });
+            return false;
+        });
+    }
+
     detectFeatures();
     enableScrollHistory();
     enableAdvancedPaging();
+    enableDeleteButtons();
 });
 
 function markTop() {

http://git-wip-us.apache.org/repos/asf/allura/blob/911d1bac/ForgeActivity/forgeactivity/templates/timeline.html
----------------------------------------------------------------------
diff --git a/ForgeActivity/forgeactivity/templates/timeline.html b/ForgeActivity/forgeactivity/templates/timeline.html
index 499fe03..c2365d2 100644
--- a/ForgeActivity/forgeactivity/templates/timeline.html
+++ b/ForgeActivity/forgeactivity/templates/timeline.html
@@ -20,7 +20,7 @@
 {% import 'forgeactivity:templates/macros.html' as am with context %}
 
 {% for a in timeline %}
-<li id="{{a._id}}" data-page="{{page}}">
+<li id="{{a._id}}" data-page="{{page}}" {% if h.has_access(c.project.neighborhood, 'admin')
%}data-can-delete{% endif %}>
   <time datetime="{{a.published|datetimeformat}}" title="{{a.published|datetimeformat}}">{{h.ago(a.published,
show_date_after=None)}}</time>
   <h1>
       {{ am.icon(a.actor, 32, 'avatar') }}

http://git-wip-us.apache.org/repos/asf/allura/blob/911d1bac/ForgeActivity/forgeactivity/tests/functional/test_root.py
----------------------------------------------------------------------
diff --git a/ForgeActivity/forgeactivity/tests/functional/test_root.py b/ForgeActivity/forgeactivity/tests/functional/test_root.py
index 240ed7d..a2a812e 100644
--- a/ForgeActivity/forgeactivity/tests/functional/test_root.py
+++ b/ForgeActivity/forgeactivity/tests/functional/test_root.py
@@ -15,13 +15,16 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
-from mock import patch
 from textwrap import dedent
-from tg import config
 
+from mock import patch
+from tg import config
+from bson import ObjectId
 import dateutil.parser
 from nose.tools import assert_equal
 from pylons import app_globals as g
+from activitystream.storage.mingstorage import Activity
+from ming.odm import ThreadLocalODMSession
 
 from allura import model as M
 from alluratest.controller import TestController
@@ -411,3 +414,67 @@ class TestActivityController(TestController):
         assert_equal(
             activity.find('{http://www.w3.org/2005/Atom}link').get('href'),
             'http://localhost/p/test/tickets/34/?limit=25#ed7c')
+
+    @td.with_tool('u/test-user-1', 'activity')
+    @td.with_user_project('test-user-1')
+    def test_delete_item_denied(self):
+        self.app.post('/u/test-user-1/activity/delete_item',
+                      {'activity_id': str(ObjectId())},
+                      status=403)
+
+    @td.with_tool('u/test-user-1', 'activity')
+    @td.with_user_project('test-user-1')
+    def test_delete_item_gone(self):
+        self.app.post('/u/test-user-1/activity/delete_item',
+                      {'activity_id': str(ObjectId())},
+                      extra_environ={'username': 'root'},  # nbhd admin
+                      status=410)
+
+    @td.with_tool('u/test-user-1', 'activity')
+    @td.with_user_project('test-user-1')
+    def test_delete_item_success(self):
+        activity_data = {
+            "obj": {
+                "activity_extras": {
+                    "summary": "Sensitive private info, oops"
+                },
+                "activity_url": "/p/test/tickets/34/?limit=25#ed7c",
+                "activity_name": "a comment"
+            },
+            "target": {
+                "activity_extras": {
+                    "allura_id": "Ticket:529f57a6033c5e5985db2efa",
+                    "summary": "Make activitystream timeline look better"
+                },
+                "activity_url": "/p/test/tickets/34/",
+                "activity_name": "ticket #34"
+            },
+            "actor": {
+                "activity_extras": {
+                    "icon_url": "/u/test-admin/user_icon",
+                    "allura_id": "User:521f96cb033c5e2587adbdff"
+                },
+                "activity_url": "/u/test-admin/",
+                "activity_name": "Administrator 1",
+                "node_id": "User:521f96cb033c5e2587adbdff"
+            },
+            "verb": "posted",
+            "published": dateutil.parser.parse("2013-12-04T21:48:19.817"),
+            "score": 1386193699,
+            "node_id": "Project:527a6584033c5e62126f5a60",
+            "owner_id": "Project:527a6584033c5e62126f5a60"
+        }
+        activity = Activity(**activity_data)
+        activity2 = Activity(**dict(activity_data, node_id='Project:123', owner_id='User:456'))
+        activity3 = Activity(**dict(activity_data, node_id='User:abc', owner_id='User:abc'))
+        ThreadLocalODMSession.flush_all()
+        activity_id = str(activity._id)
+        assert_equal(Activity.query.find({'obj.activity_extras.summary': 'Sensitive private
info, oops'}).count(), 3)
+
+        self.app.post('/u/test-user-1/activity/delete_item',
+                      {'activity_id': activity_id},
+                      extra_environ={'username': 'root'},  # nbhd admin
+                      status=200)
+        ThreadLocalODMSession.flush_all()
+
+        assert_equal(Activity.query.find({'obj.activity_extras.summary': 'Sensitive private
info, oops'}).count(), 0)

http://git-wip-us.apache.org/repos/asf/allura/blob/911d1bac/requirements.txt
----------------------------------------------------------------------
diff --git a/requirements.txt b/requirements.txt
index eba6442..8c8db42 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -21,7 +21,7 @@ httplib2==0.7.4
 iso8601==0.1.4
 Jinja2==2.9.6
 Markdown==2.2.1
-Ming==0.5.5
+Ming==0.5.6
 oauth2==1.5.170
 # tg2 dep PasteDeploy must specified before TurboGears2, to avoid a version/allow-hosts problem
 Paste==1.7.5.1


Mime
View raw message