Milimetric has submitted this change and it was merged.

Change subject: Add ability to remove cohorts from database.
......................................................................


Add ability to remove cohorts from database.

Owners can remove their cohorts from the database. A warning will show if there
are other users sharing the cohort. When deleted, viewers records will be
deleted from their personal list. If a viewer deletes a cohort, it will only
delete the cohort from their personal list, not from the cohort database.

Change-Id: I431cad933d5b888f10fe5533b75e7b1776649ab3
---
M tests/test_controllers/test_cohorts.py
M wikimetrics/controllers/cohorts.py
M wikimetrics/exceptions.py
M wikimetrics/static/js/cohortList.js
M wikimetrics/static/js/site.js
M wikimetrics/templates/cohorts.html
6 files changed, 246 insertions(+), 26 deletions(-)

Approvals:
  Milimetric: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/tests/test_controllers/test_cohorts.py 
b/tests/test_controllers/test_cohorts.py
index e86df9d..e8cb362 100644
--- a/tests/test_controllers/test_cohorts.py
+++ b/tests/test_controllers/test_cohorts.py
@@ -1,12 +1,12 @@
 import json
 import time
 from StringIO import StringIO
-from nose.tools import assert_equal, assert_true, assert_false, raises, nottest
+from nose.tools import assert_equal, assert_not_equal, assert_true, 
assert_false
 from wikimetrics.configurables import app
 from tests.fixtures import WebTest
 from wikimetrics.models import (
     Cohort, CohortUser, CohortUserRole, ValidateCohort,
-    CohortWikiUser, WikiUser,
+    CohortWikiUser, WikiUser, User
 )
 
 
@@ -135,26 +135,127 @@
         assert_true(
             response.data.find('Validating cohort') >= 0
         )
-    
-    def test_delete_cohort(self):
+
+    def test_delete_cohort_owner_no_viewer(self):
         response = self.app.post('/cohorts/delete/{0}'.format(self.cohort.id))
-        
+
         assert_equal(response.status_code, 200)
         assert_true(response.data.find('isRedirect') >= 0)
         assert_true(response.data.find('/cohorts/') >= 0)
+        cohort_id = self.cohort.id
         self.session.commit()
+
+        # Check that all relevant rows are deleted
+        cwu = self.session.query(CohortWikiUser) \
+            .filter(CohortWikiUser.cohort_id == cohort_id) \
+            .first()
+        assert_equal(cwu, None)
         cu = self.session.query(CohortUser) \
-            .filter(CohortUser.cohort_id == self.cohort.id) \
+            .filter(CohortUser.cohort_id == cohort_id) \
+            .filter(CohortUser.user_id == self.owner_user_id) \
             .first()
         assert_equal(cu, None)
-    
+        wu = self.session.query(WikiUser) \
+            .filter(WikiUser.validating_cohort == cohort_id) \
+            .first()
+        assert_equal(wu, None)
+        c = self.session.query(Cohort).get(cohort_id)
+        assert_equal(c, None)
+
+    def test_delete_cohort_owner_has_viewer(self):
+        viewer_user = User()
+        self.session.add(viewer_user)
+        self.session.commit()
+
+        viewer_cohort_user = CohortUser(
+            user_id=viewer_user.id,
+            cohort_id=self.cohort.id,
+            role=CohortUserRole.VIEWER
+        )
+        self.session.add(viewer_cohort_user)
+        self.session.commit()
+        response = self.app.post('/cohorts/delete/{0}'.format(self.cohort.id))
+
+        assert_equal(response.status_code, 200)
+        assert_true(response.data.find('isRedirect') >= 0)
+        assert_true(response.data.find('/cohorts/') >= 0)
+        cohort_id = self.cohort.id
+        self.session.commit()
+
+        # Check that all relevant rows are deleted
+        cwu = self.session.query(CohortWikiUser) \
+            .filter(CohortWikiUser.cohort_id == cohort_id) \
+            .first()
+        assert_equal(cwu, None)
+        cu = self.session.query(CohortUser) \
+            .filter(CohortUser.cohort_id == cohort_id) \
+            .first()
+        assert_equal(cu, None)
+        wu = self.session.query(WikiUser) \
+            .filter(WikiUser.validating_cohort == cohort_id) \
+            .first()
+        assert_equal(wu, None)
+        c = self.session.query(Cohort).get(cohort_id)
+        assert_equal(c, None)
+
+    def test_delete_cohort_as_viewer(self):
+        # Changing the owner_user_id to a VIEWER
+        self.session.query(CohortUser) \
+            .filter(CohortUser.user_id == self.owner_user_id) \
+            .filter(CohortUser.cohort_id == self.cohort.id) \
+            .update({'role': CohortUserRole.VIEWER})
+        self.session.commit()
+
+        # Adding a different CohortUser as owner
+        new_cohort_user = CohortUser(
+            cohort_id=self.cohort.id,
+            role=CohortUserRole.OWNER
+        )
+        self.session.add(new_cohort_user)
+        self.session.commit()
+
+        response = self.app.post('/cohorts/delete/{0}'.format(self.cohort.id))
+
+        assert_equal(response.status_code, 200)
+        assert_true(response.data.find('isRedirect') >= 0)
+        assert_true(response.data.find('/cohorts/') >= 0)
+        cohort_id = self.cohort.id
+        self.session.commit()
+
+        # Check that all relevant rows are deleted
+        cwu = self.session.query(CohortWikiUser) \
+            .filter(CohortWikiUser.cohort_id == cohort_id) \
+            .first()
+        assert_not_equal(cwu, None)
+        cu = self.session.query(CohortUser) \
+            .filter(CohortUser.cohort_id == cohort_id) \
+            .filter(CohortWikiUser.id == self.owner_user_id) \
+            .first()
+        assert_equal(cu, None)
+        wu = self.session.query(WikiUser) \
+            .filter(WikiUser.validating_cohort == cohort_id) \
+            .first()
+        assert_not_equal(wu, None)
+        c = self.session.query(Cohort).get(cohort_id)
+        assert_not_equal(c, None)
+
     def test_delete_unauthorized_cohort(self):
         self.session.query(CohortUser).delete()
         self.session.commit()
         response = self.app.post('/cohorts/delete/{0}'.format(self.cohort.id))
-        
+
         assert_true(response.data.find('isError') >= 0)
-        assert_true(response.data.find('This Cohort can not be deleted') >= 0)
+        assert_true(response.data.find('No role found in cohort user.') >= 0)
+
+    def test_delete_cohort_database_error(self):
+        self.session.query(CohortWikiUser).delete()
+        self.session.commit()
+        response = self.app.post('/cohorts/delete/{0}'.format(self.cohort.id))
+
+        expected_message = 'Owner attempt to delete a cohort failed. ' \
+                           'Cannot delete CohortWikiUser.'
+        assert_true(response.data.find('isError') >= 0)
+        assert_true(response.data.find(expected_message) >= 0)
 
 
 class CohortsControllerUploadTest(WebTest):
diff --git a/wikimetrics/controllers/cohorts.py 
b/wikimetrics/controllers/cohorts.py
index f3eec14..984bff4 100644
--- a/wikimetrics/controllers/cohorts.py
+++ b/wikimetrics/controllers/cohorts.py
@@ -17,6 +17,7 @@
     User, WikiUser, CohortWikiUser, MediawikiUser,
     ValidateCohort
 )
+from ..exceptions import DatabaseError
 
 
 @app.route('/cohorts/')
@@ -127,6 +128,7 @@
         cohort_dict['total_count'] = len(cohort_dict['wikiusers'])
         cohort_dict['valid_count'] = cohort_dict['total_count']
         cohort_dict['invalid_count'] = 0
+        cohort_dict['delete_message'] = None
         return cohort_dict
     
     validation_task = ValidateCohort.task.AsyncResult(task_key)
@@ -147,6 +149,14 @@
         cohort_dict['total_count'] = session.query(func.count(WikiUser)) \
             .filter(WikiUser.validating_cohort == cohort_dict['id']) \
             .one()[0]
+        users = num_users(session, cohort_dict['id'])
+        non_owners = users - 1
+        role = get_role(session, cohort_dict['id'])
+        if users != 1 and role == CohortUserRole.OWNER:
+            cohort_dict['delete_message'] = 'delete this cohort? ' + \
+                'There are {0} other user(s) shared on this 
cohort.'.format(non_owners)
+        else:
+            cohort_dict['delete_message'] = 'delete this cohort?'
     finally:
         session.close()
     return cohort_dict
@@ -227,23 +237,122 @@
         session.close()
 
 
+def num_users(session, cohort_id):
+    user_count = session.query(CohortUser) \
+        .filter(CohortUser.cohort_id == cohort_id) \
+        .count()
+    return user_count
+
+
+def get_role(session, cohort_id):
+    """
+    Returns the role of the current user.
+    """
+    try:
+        cohort_user = session.query(CohortUser.role) \
+            .filter(CohortUser.cohort_id == cohort_id) \
+            .filter(CohortUser.user_id == current_user.id) \
+            .one()[0]
+        return cohort_user
+    except:
+        session.rollback()
+        raise DatabaseError('No role found in cohort user.')
+
+
+def delete_viewer_cohort(session, cohort_id):
+    """
+    Used when deleting a user's connection to a cohort. Currently used when 
user
+    is a VIEWER of a cohort and want to remove that cohort from their list.
+
+    Raises exception when viewer is duplicated, nonexistent, or can not be 
deleted.
+    """
+    cu = session.query(CohortUser) \
+        .filter(CohortUser.cohort_id == cohort_id) \
+        .filter(CohortUser.user_id == current_user.id) \
+        .filter(CohortUser.role == CohortUserRole.VIEWER) \
+        .delete()
+    
+    if cu != 1:
+        session.rollback()
+        raise DatabaseError('Viewer attempt delete cohort failed.')
+
+
+def delete_owner_cohort(session, cohort_id):
+    """
+    Deletes the cohort and all associate records with that cohort if user is 
the
+    owner.
+
+    Raises an error if it cannot delete the cohort.
+    """
+    # Check that there's only one owner and delete it
+    cu = session.query(CohortUser) \
+        .filter(CohortUser.cohort_id == cohort_id) \
+        .filter(CohortUser.role == CohortUserRole.OWNER) \
+        .delete()
+
+    if cu != 1:
+        session.rollback()
+        raise DatabaseError('No owner or multiple owners in cohort.')
+    else:
+        try:
+            # Delete all other non-owners from cohort_user
+            session.query(CohortUser) \
+                .filter(CohortUser.cohort_id == cohort_id) \
+                .delete()
+            cwu = session.query(CohortWikiUser) \
+                .filter(CohortWikiUser.cohort_id == cohort_id) \
+                .delete()
+            if cwu < 1:
+                raise DatabaseError('Cannot delete CohortWikiUser.')
+
+            wu = session.query(WikiUser) \
+                .filter(WikiUser.validating_cohort == cohort_id) \
+                .delete()
+            if wu < 1:
+                raise DatabaseError('Cannot delete WikiUser.')
+
+            c = session.query(Cohort) \
+                .filter(Cohort.id == cohort_id) \
+                .delete()
+            if c < 1:
+                raise DatabaseError('Cannot delete Cohort.')
+        except DatabaseError as e:
+            print "here3"
+            session.rollback()
+            raise DatabaseError('Owner attempt to delete a cohort failed. ' + 
e.message)
+
+
 @app.route('/cohorts/delete/<int:cohort_id>', methods=['POST'])
 def delete_cohort(cohort_id):
     """
-    Deletes a cohort and all its wikiusers if it belongs to only current_user
+    Deletes a cohort and all its associated links if it belongs to only 
current_user
     Removes the relationship between current_user and this cohort if it belongs
-    to more than just current_user
+    to a user other than current_user
     """
     session = db.get_session()
     try:
-        result = session.query(CohortUser) \
-            .filter(CohortUser.cohort_id == cohort_id) \
-            .filter(CohortUser.user_id == current_user.id) \
-            .delete()
-        session.commit()
-        if result > 0:
+        owner_and_viewers = num_users(session, cohort_id)
+        role = get_role(session, cohort_id)
+
+        # Owner wants to delete, no other viewers or
+        # Owner wants to delete, have other viewers, delete from other 
viewer's lists too
+        if owner_and_viewers >= 1 and role == CohortUserRole.OWNER:
+            delete_owner_cohort(session, cohort_id)
+            session.commit()
             return json_redirect(url_for('cohorts_index'))
+
+        # Viewer wants to delete cohort from their list, doesn't delete cohort 
from db;l,
+        elif owner_and_viewers > 1 and role == CohortUserRole.VIEWER:
+            delete_viewer_cohort(session, cohort_id)
+            session.commit()
+            return json_redirect(url_for('cohorts_index'))
+
+        # None of the other cases fit.
         else:
-            return json_error('This Cohort can not be deleted')
+            session.rollback()
+            return json_error('This Cohort can not be deleted.')
+    except DatabaseError as e:
+        session.rollback()
+        return json_error(e.message)
     finally:
         session.close()
diff --git a/wikimetrics/exceptions.py b/wikimetrics/exceptions.py
index a3dd27b..5b3710a 100644
--- a/wikimetrics/exceptions.py
+++ b/wikimetrics/exceptions.py
@@ -18,3 +18,10 @@
     
     """
     pass
+
+
+class DatabaseError(Exception):
+    """
+    Thrown when database calls don't return an expected result.
+    """
+    pass
diff --git a/wikimetrics/static/js/cohortList.js 
b/wikimetrics/static/js/cohortList.js
index e2d2968..9559a94 100644
--- a/wikimetrics/static/js/cohortList.js
+++ b/wikimetrics/static/js/cohortList.js
@@ -12,6 +12,7 @@
             cohort.total_count(data.total_count);
             cohort.validation_status(data.validation_status);
             cohort.wikiusers(data.wikiusers);
+            cohort.delete_message(data.delete_message);
         },
         
         view: function(cohort, event, callback){
@@ -35,7 +36,7 @@
         },
         
         deleteCohort: function(cohort, event){
-            if (site.confirmDanger(event)){
+            if (site.confirmDanger(event, true)){
                 $.post('/cohorts/delete/' + cohort.id)
                     .done(site.handleWith(function(){
                         site.showWarning('Something is wrong, you should be 
redirected');
@@ -43,7 +44,7 @@
                     .fail(site.failure);
             }
         },
-        
+
         validateWikiusers: function(cohort, event){
             if (site.confirmDanger(event)){
                 $.post('/cohorts/validate/' + cohort.id)
@@ -90,6 +91,7 @@
             item.valid_count = ko.observable(0);
             item.total_count = ko.observable(0);
             item.validation_status = ko.observable();
+            item.delete_message = ko.observable();
         });
     }
 });
diff --git a/wikimetrics/static/js/site.js b/wikimetrics/static/js/site.js
index a75218f..e13c06f 100644
--- a/wikimetrics/static/js/site.js
+++ b/wikimetrics/static/js/site.js
@@ -21,9 +21,9 @@
         return true;
     },
     
-    confirmDanger: function(event){
+    confirmDanger: function(event, noQuestion){
         var title = $(event.target).attr('title');
-        return confirm('Are you sure you want to ' + title + '?');
+        return confirm('Are you sure you want to ' + title + (noQuestion ? '' 
: '?') );
     },
     
     showError: function (message, permanent){
diff --git a/wikimetrics/templates/cohorts.html 
b/wikimetrics/templates/cohorts.html
index acf4834..a8289ca 100644
--- a/wikimetrics/templates/cohorts.html
+++ b/wikimetrics/templates/cohorts.html
@@ -21,9 +21,13 @@
             <div class="hero-unit">
                 <h5 data-bind="text: name"></h5>
                 <p data-bind="text: description"></p>
-                <div data-bind="if: validated() && valid_count() > 0">
+                <div class="btn-group pull-left" data-bind="if: validated() && 
valid_count() > 0">
                     <a 
data-bind="attr:{href:'{{url_for('reports_request')}}#'+id}"
                        class="btn btn-primary">Create Report</a>
+                </div>
+                <div class="btn-group">
+                    <a data-bind="click: $root.deleteCohort, attr: { title: 
delete_message }"
+                       class="btn btn-danger">Remove Cohort</a>
                 </div>
                 <div data-bind="if: validated_count() === total_count() && 
!validated()">
                     <hr/>
@@ -44,9 +48,6 @@
                 <div data-bind="if: valid_count() < total_count()">
                     <hr/>
                     <p>
-                        <a data-bind="click: $root.deleteCohort"
-                           class="btn btn-danger"
-                           title="remove this cohort from your list">Remove 
Cohort</a>
                         <a data-bind="click: $root.validateWikiusers"
                            class="btn btn-primary"
                            title="cancel and restart validation for this 
cohort">Validate Again</a>

-- 
To view, visit https://gerrit.wikimedia.org/r/119343
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I431cad933d5b888f10fe5533b75e7b1776649ab3
Gerrit-PatchSet: 19
Gerrit-Project: analytics/wikimetrics
Gerrit-Branch: master
Gerrit-Owner: Terrrydactyl <tcho...@gmail.com>
Gerrit-Reviewer: Milimetric <dandree...@wikimedia.org>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to