Skip to content

Commit

Permalink
RDISCROWD-5372 User responses looks the same even they did not give t…
Browse files Browse the repository at this point in the history
…he same answer (#774)

* RDISCROWD-5372 User responses looks the same even they did not give the same answer

* per code review

* typo
  • Loading branch information
XiChenn committed Sep 9, 2022
1 parent d702b92 commit fd29f97
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 19 deletions.
61 changes: 48 additions & 13 deletions pybossa/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -1225,21 +1225,22 @@ def admin_or_project_owner(user, project):
def process_tp_components(tp_code, user_response):
"""grab the 'pyb-answer' value and use it as a key to retrieve the response
from user_response(a dict). The response data is then used to set the
'initial-value' or ':initial-value' based on different components"""
:initial-value' """
soup = BeautifulSoup(tp_code, 'html.parser')

# Disable autosave so that response for different users will be different
task_presenter_elements = soup.find_all("task-presenter")
for task_presenter in task_presenter_elements:
remove_element_attributes(task_presenter, "auto-save-seconds")
remove_element_attributes(task_presenter, "allow-save-work")

for tp_component_tag in TP_COMPONENT_TAGS:
tp_components = soup.find_all(tp_component_tag)
for tp_component in tp_components:
response_key = tp_component.get('pyb-answer')
response_key = get_first_existing_data(tp_component, "pyb-answer")
response_value = user_response.get(response_key, '')
if tp_component.name in ["checkbox-input", "multi-select-input"]:
if type(response_value) is bool:
response_value = str(response_value).lower()
elif type(response_value) is list:
response_value = '[' + ",".join(map(lambda s: '"' + s + '"', response_value)) + ']'
tp_component[':initial-value'] = response_value
else:
tp_component['initial-value'] = response_value
remove_element_attributes(tp_component, "initial-value")
tp_component[':initial-value'] = json.dumps(response_value)
return soup.prettify()


Expand All @@ -1260,7 +1261,7 @@ def process_table_component(tp_code, user_response, task):
response_values = list(response_values.values())

# handle existing data for the response
initial_data_str = table_element[':data']
initial_data_str = get_first_existing_data(table_element, "data")
existing_data = [] # holds reqeust fields or data from :initial_data
if initial_data_str:
if initial_data_str.startswith("task.info"): # e.g. task.info.a.b.c
Expand All @@ -1285,15 +1286,49 @@ def process_table_component(tp_code, user_response, task):
if len(existing_data) > len(response_values):
response_values.extend(existing_data[len(response_values):])

# Remove attributes like :data, v-bind:data and data before appending
remove_element_attributes(table_element, "data")
table_element[':data'] = json.dumps(response_values)

# remove initial-value attribute so that table can display the data
# append ":initial-value="props.row.COL_NAME" to the element and also
tag_list = table_element.find_all(
lambda tag: "pyb-table-answer" in tag.attrs)
for t in tag_list:
if "initial-value" in t.attrs:
del t["initial-value"]
remove_element_attributes(t, "initial-value")
column_name = t.get("pyb-table-answer", "")
t[":initial-value"] = "props.row." + column_name
return soup.prettify()


def get_first_existing_data(page_element, attribute):
"""
Get the first non None data from a serials of attributes from a
page_element. e.g. if an attribute is "data", a serials of attributes is
defined as [":data", "v-bind:data", "data"]
:param page_element: A PageElement.
:param attribute: an attribute of the page_element.
:return: the corresponding value of the attribute
:rtype: string
"""
attributes = [f":{attribute}", f"v-bind:{attribute}", attribute]
values = [page_element.get(attr) for attr in attributes]
data = next((v for v in values if v), None)
return data


def remove_element_attributes(page_element, attribute):
"""
Remove a serials of attributes from a page_element.
e.g. if an attribute is "data", a serials of attributes is defined as
[":data", "v-bind:data", "data"]
:param page_element: A PageElement.
:param attribute: an attribute of the page_element.
:return: None.
"""
attributes = [f":{attribute}", f"v-bind:{attribute}", attribute]
for attr in attributes:
if page_element.has_attr(attr):
del page_element[attr]
10 changes: 10 additions & 0 deletions pybossa/view/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -1200,6 +1200,16 @@ def make_random_task_gold(short_name):
@blueprint.route('/<short_name>/task/<int:task_id>/<int:task_submitter_id>')
@login_required
def task_presenter(short_name, task_id, task_submitter_id=None):
"""
Displaying task presenter code. There are two endpoints. One for submitting
task (including read-only viewing tasks and cherry-pick modes) and the other
for viewing user's completed response (with task_submitter_id in the
endpoint)
:param short_name: project's short name
:param task_id: task ID
:param task_submitter_id: task submitter's user id. It is received only upon
viewing the task answers by the user
"""
mode = request.args.get('mode')
project, owner, ps = project_by_shortname(short_name)
ensure_authorized_to('read', project)
Expand Down
44 changes: 38 additions & 6 deletions test/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from datetime import datetime, timedelta
from unittest.mock import patch, MagicMock

from bs4 import BeautifulSoup
from flask_wtf import FlaskForm as Form
from nose.tools import nottest, assert_raises

Expand Down Expand Up @@ -956,7 +957,8 @@ def test_admin_or_project_owner_raises_forbidden(self):
assert_raises(Forbidden, admin_or_project_owner, user, project)

def test_process_tp_components(self):
tp_code = """ <task-presenter>
tp_code = """ <task-presenter auto-save-seconds="15" />
<task-presenter :auto-save-seconds="15" v-bind:allow-save-work="true">
<text-input id='_kp6zwx2rs' type='text' :validations='[]' pyb-answer='freeText' initial-value='nothing special'></text-input>
<div class="row">
Expand All @@ -977,7 +979,7 @@ def test_process_tp_components(self):
<div id="_e9pm92ges">
<div class="checkbox">
<label for="_mg59znxa7">
<checkbox-input :initial-value="false" id="_mg59znxa7" pyb-answer="isRelevant"></checkbox-input> Is this document relevant?
<checkbox-input v-bind:initial-value="false" id="_mg59znxa7" pyb-answer="isRelevant"></checkbox-input> Is this document relevant?
</label>
</div>
</div>
Expand All @@ -994,10 +996,12 @@ def test_process_tp_components(self):
user_response = {'answer': 'Japanese', 'freeText': 'This is cool', 'subjects': ['Social Study', 'English', 'Math'], 'isNewData': 'yes', 'isRelevant': False}
result = util.process_tp_components(tp_code, user_response)

assert """<dropdown-input :choices='{"yes":"Yes","no":"No"}' :validations='["required"]' initial-value="yes" pyb-answer="isNewData">""" in result
assert """<radio-group-input :choices='{"Chinese":"Chinese","Korean":"Korean","Japanese":"Japanese"}' :validations='["required"]' initial-value="Japanese" name="userAnswer" pyb-answer="answer">""" in result
assert """<dropdown-input :choices='{"yes":"Yes","no":"No"}' :initial-value='"yes"' :validations='["required"]' pyb-answer="isNewData">""" in result
assert """<radio-group-input :choices='{"Chinese":"Chinese","Korean":"Korean","Japanese":"Japanese"}' :initial-value='"Japanese"' :validations='["required"]' name="userAnswer" pyb-answer="answer">""" in result
assert """<checkbox-input :initial-value="false" id="_mg59znxa7" pyb-answer="isRelevant">""" in result
assert """<multi-select-input :choices='["Math","English","Social Study","Python"]' :initial-value='["Social Study","English","Math"]' :validations='["required"]' pyb-answer="subjects">""" in result
assert """<multi-select-input :choices='["Math","English","Social Study","Python"]' :initial-value='["Social Study", "English", "Math"]' :validations='["required"]' pyb-answer="subjects">""" in result
assert "auto-save-seconds" not in result
assert "allow-save-work" not in result

@with_request_context
def test_process_table_component(self):
Expand All @@ -1006,7 +1010,7 @@ def test_process_table_component(self):
<table-element
:key='task.id'
name='all_info'
:data='[{"name":"", "key_not_in_response": "should_show_in_response"}]'
v-bind:data='[{"name":"", "key_not_in_response": "should_show_in_response"}]'
:columns='["name","position","phoneNumber","emailAddress","physicalLocation","linkedIn","zoomInfo","moreInfo"]'
:options='{
"headings": {
Expand Down Expand Up @@ -1213,6 +1217,34 @@ def test_process_table_component_with_initial_data_from_task(self):
assert ':initial-value="props.row.QC_Reason"' in result
assert ':initial-value="props.row.QC_Notes"' in result

def test_get_first_existing_data(self):
tag = BeautifulSoup('<b id="boldest">bold</b>', 'html.parser').b
assert util.get_first_existing_data(tag, "id") == "boldest"

tag = BeautifulSoup('<b :id="boldest">bold</b>', 'html.parser').b
assert util.get_first_existing_data(tag, "id") == "boldest"

tag = BeautifulSoup('<b v-bind:id="boldest">bold</b>', 'html.parser').b
assert util.get_first_existing_data(tag, "id") == "boldest"

def test_remove_element_attributes(self):
tag = BeautifulSoup('<b id="boldest">bold</b>', 'html.parser').b
util.remove_element_attributes(tag, "id")
assert not tag.has_attr("id")

tag = BeautifulSoup('<b :id="boldest">bold</b>', 'html.parser').b
util.remove_element_attributes(tag, "id")
assert not tag.has_attr(":id")

tag = BeautifulSoup('<b v-bind:id="boldest">bold</b>', 'html.parser').b
util.remove_element_attributes(tag, "id")
assert not tag.has_attr("v-bind:id")

tag = BeautifulSoup('<b :id="boldest" id="a">bold</b>', 'html.parser').b
util.remove_element_attributes(tag, "id")
assert not tag.has_attr(":id")
assert not tag.has_attr("id")


class TestIsReservedName(object):
from test import flask_app as app
Expand Down

0 comments on commit fd29f97

Please sign in to comment.