Skip to content

Commit

Permalink
RDISCROWD-5651: Enable browse task for regular user (#811)
Browse files Browse the repository at this point in the history
* enable task browse for regular user

* update themes to enable customized column for all scheudulers

* add tests. fix state filter

* update themes

* rebase to main, update themes
  • Loading branch information
dchhabda committed Feb 10, 2023
1 parent b917bc4 commit 2b9efc6
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 8 deletions.
15 changes: 10 additions & 5 deletions pybossa/cache/task_browse_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,20 @@ def get_task_filters(args):
user_pref_db_clause = get_user_pref_db_clause(user_pref)
filters += " AND ( {} )".format(user_pref_db_clause)

# for task queue
if args.get("filter_by_wfilter_upref"):
# for regular user, only include tasks that user has worked on
if args.get("allow_taskrun_edit"):
filters += ''' AND EXISTS
(SELECT 1 FROM task_run WHERE project_id=:project_id AND
user_id=:user_id AND task_id=task.id)'''
params["user_id"] = args.get('user_id')
elif args.get("filter_by_wfilter_upref"): # for task queue
# task queue exclude completed tasks
filters += " AND state!='completed'"

# exclude tasks that the current worker has worked on before
filters += ''' AND NOT EXISTS
(SELECT 1 FROM task_run WHERE project_id=:project_id AND
user_id=:user_id AND task_id=task.id)'''
(SELECT 1 FROM task_run WHERE project_id=:project_id AND
user_id=:user_id AND task_id=task.id)'''

params["user_id"] = args.get('user_id')

# include additional filters
Expand Down
27 changes: 25 additions & 2 deletions pybossa/view/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -1309,6 +1309,10 @@ def task_presenter(short_name, task_id, task_submitter_id=None):
# Allow lock when scheduler is task_queue and user is a worker or user is admin/subadminm/coowner in task view.
if scheduler == sched.Schedulers.task_queue and mode == "cherry_pick":
lock_task_for_user(task_id, project.id, current_user.id)
elif project.info.get("allow_taskrun_edit") and task_submitter_id:
# with project with edit submissions enabled and task_submitter_id passed
# task response would be displayed later
pass
elif not sched.can_read_task(task, current_user):
raise abort(403)

Expand Down Expand Up @@ -1601,6 +1605,9 @@ def tasks_browse(short_name, page=1, records_per_page=None):
title = project_title(project, "Tasks")
pro = pro_features()
allowed_records_per_page = [10, 20, 30, 50, 70, 100]
admin_subadmin_coowner = current_user.subadmin or current_user.admin or current_user.id in project.owners_ids
regular_user = not admin_subadmin_coowner
allow_taskrun_edit = project.info.get("allow_taskrun_edit") or False

try:
columns = get_searchable_columns(project.id)
Expand All @@ -1615,11 +1622,24 @@ def tasks_browse(short_name, page=1, records_per_page=None):
view_type = request.args.get('view')
task_browse_default_records_per_page = 10
task_list_default_records_per_page = 30
if view_type != 'tasklist' and (current_user.subadmin or current_user.admin or current_user.id in project.owners_ids):
if view_type != 'tasklist' and admin_subadmin_coowner:
# owners and (sub)admin have full access, default size page for owner view is 10
per_page = records_per_page if records_per_page in allowed_records_per_page else task_browse_default_records_per_page
# parse args
args = parse_tasks_browse_args(request.args.to_dict())
elif view_type != 'tasklist' and allow_taskrun_edit and regular_user:
# browse tasks for a regular user to be available when
# 1. project is configured to allow editing of task runs
# 2. browse task request is not for task list
dict_args = request.args.to_dict()
# show columns that are permitted for regular users
dict_args["display_columns"] = ["task_id", "priority", "created"]
# show task.info columns that are configured under tasklist_columns
dict_args["display_info_columns"] = project.info.get('tasklist_columns', [])
args = parse_tasks_browse_args(dict_args)
args["user_id"] = current_user.id
args["allow_taskrun_edit"] = True
per_page = records_per_page if records_per_page in allowed_records_per_page else task_browse_default_records_per_page
elif scheduler == Schedulers.task_queue:
# worker can access limited tasks only when task_queue_scheduler is selected
user = cached_users.get_user_by_id(current_user.id)
Expand Down Expand Up @@ -1647,6 +1667,7 @@ def tasks_browse(short_name, page=1, records_per_page=None):
args["sql_params"] = dict(assign_user=json.dumps({'assign_user': [user_email]}))
args["display_columns"] = ['task_id', 'priority', 'created']
args["view"] = view_type
args["regular_user"] = regular_user
# default page size for worker view is 100
per_page = records_per_page if records_per_page in allowed_records_per_page else task_list_default_records_per_page
else:
Expand Down Expand Up @@ -1751,7 +1772,9 @@ def get_users_completed(task):
location_options=location_options,
reserved_options=RESERVED_TASKLIST_COLUMNS,
rdancy_upd_exp=rdancy_upd_exp,
can_know_task_is_gold=can_know_task_is_gold)
can_know_task_is_gold=can_know_task_is_gold,
allow_taskrun_edit=allow_taskrun_edit,
regular_user=regular_user)

return handle_content_type(data)

Expand Down
15 changes: 15 additions & 0 deletions test/test_cache/test_cache_projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,21 @@ def test_task_browse_get_task_filters(self):
assert filters == expected_filter_query, filters
assert params == expected_params, params

# with allow_taskrun_edit, user submitted responses to be returned
user_submitted_responses = "(SELECT 1 FROM task_run WHERE project_id=:project_id AND\n user_id=:user_id AND task_id=task.id)"
expected_user_id = 239
filters = dict(task_id=1, allow_taskrun_edit=True, order_by='task_id', user_id=expected_user_id)
filters, params = get_task_filters(filters)
assert filters.find(user_submitted_responses) > -1, "only user submitted responses to be returned by the query"
assert params["user_id"] == expected_user_id, "user id to be present to filter user responses by id"

filters = dict(task_id=1, allow_taskrun_edit=False, order_by='task_id', user_id=239)
filters, params = get_task_filters(filters)
assert filters.find(user_submitted_responses) == -1, "user submitted responses NOT to be returned by the query"
assert "user_id" not in params



def test_task_browse_gold_task_filters(self):
filters = dict(task_id=1,hide_completed=True, gold_task='1', order_by='task_id')
expected_filter_query = " AND task.id = :task_id AND task.state='ongoing' AND task.calibration = :calibration"
Expand Down
83 changes: 83 additions & 0 deletions test/test_web.py
Original file line number Diff line number Diff line change
Expand Up @@ -3400,6 +3400,67 @@ def test_task_presenter(self):
res = self.app.get('/project/%s/task/%s/%s' % (project_short_name, task.id, user.id))
assert b'TaskPresenter' in res.data

@with_context
def test_task_presenter_with_allow_taskrun_edit_works(self):
"""Test WEB with taskrun edit permitted, get expected task based on user access"""
self.register()
self.signin()
self.create()
project = db.session.query(Project).get(1)
project.info = dict(allow_taskrun_edit=True)
db.session.commit()
self.new_task(project.id)
project_short_name = project.short_name

task = db.session.query(Task).filter(Task.project_id == 1).first()

regular_user = UserFactory.create(id=999, subadmin=False, admin=False)
regular_user.set_password('1234')
user_repo.save(regular_user)
self.signin(email=regular_user.email_addr, password='1234')
task_run = TaskRun(project_id=project.id, task_id=task.id,
info={'answer': 1,
'odfoa': {'version': 1, 'source-uri': 'http://fake.com', 'odf': {}, 'oa': {}},
'fake': {'b': 27}},
user_id=regular_user.id)
db.session.add(task_run)
db.session.commit()

# passing task_submitter_id, task response retrieved and no 403
res = self.app.get('/project/%s/task/%s/%s' % (project_short_name, task.id, regular_user.id))
assert res.status_code == 200, res.status_code

@with_context
def test_task_presenter_with_allow_taskrun_edit_raises_forbidden(self):
"""Test WEB with taskrun edit permitted, task_submitter_id not passed raises 403"""
self.register()
self.signin()
self.create()
project = db.session.query(Project).get(1)
project.info = dict(allow_taskrun_edit=True)
db.session.commit()
self.new_task(project.id)
project_short_name = project.short_name

task = db.session.query(Task).filter(Task.project_id == 1).first()
user = db.session.query(User).first()
task_run = TaskRun(project_id=project.id, task_id=task.id,
info={'answer': 1,
'odfoa': {'version': 1, 'source-uri': 'http://fake.com', 'odf': {}, 'oa': {}},
'fake': {'b': 27}},
user_id=user.id)
db.session.add(task_run)
db.session.commit()

# task_submitter_id is passed to fetch task response recorded by the user
# 403 is returned when not passing task_submitter_id
regular_user = UserFactory.create(id=999, subadmin=False, admin=False)
regular_user.set_password('1234')
user_repo.save(regular_user)
self.signin(email=regular_user.email_addr, password='1234')
res = self.app.get('/project/%s/task/%s' % (project_short_name, task.id))
assert res.status_code == 403, res.status_code

@with_context
@patch('pybossa.view.projects.uploader.upload_file', return_value=True)
def test_25_get_wrong_task_app(self, mock):
Expand Down Expand Up @@ -9301,6 +9362,28 @@ def test_task_list_worker_view_1(self, check_password):
res = self.app.get(url, follow_redirects=True)
assert res.status_code == 403, res.status_code

@with_context
@patch('pybossa.view.projects._check_if_redirect_to_password')
def test_tasks_browse_allow_taskrun_edit_works(self, check_password):
"""Test tasks browse with edit submission permitted works for regular user."""
check_password.return_value = None
admin, owner, user = UserFactory.create_batch(3)
project = ProjectFactory.create(zip_download=True, owner=owner, info={"sched": "default", "allow_taskrun_edit": True})
task = TaskFactory.create_batch(20, project=project)
url = '/project/%s/tasks/browse?api_key=%s' \
% (project.short_name, user.api_key)
res = self.app.get(url, follow_redirects=True)
assert res.status_code == 200, res.status_code
dom = BeautifulSoup(res.data)
# confirm that only "Tash #, Priority and Created are listed"
th_tags = dom.findAll("th", {"class": "sortable"})
expected_columns = ["Task #", "Priority", "Created"]
assert len(th_tags) == len(expected_columns), th_tags
th_tag_1, th_tag_2, th_tag_3 = th_tags[0].text.strip(), th_tags[1].text.strip(), th_tags[2].text.strip()
assert th_tag_1 == expected_columns[0], f"found column {th_tag_1}, expected column {expected_columns[0]} not present"
assert th_tag_2 == expected_columns[1], f"found column {th_tag_3}, expected column {expected_columns[1]} not present"
assert th_tag_3 == expected_columns[2], f"found column {th_tag_3}, expected column {expected_columns[2]} not present"

@with_context
def test_projects_account(self):
"""Test projecs on profiles are good."""
Expand Down

0 comments on commit 2b9efc6

Please sign in to comment.