Skip to content

Commit 7b82dfd

Browse files
Merge pull request #339 from biocore/csymons_skin_scoring_app_working
Skin Scoring App
2 parents c381a5f + e302620 commit 7b82dfd

File tree

4 files changed

+192
-11
lines changed

4 files changed

+192
-11
lines changed

microsetta_interface/implementation.py

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ class Source:
124124
VIOSCREEN_ID = 10001
125125
MYFOODREPO_ID = 10002
126126
POLYPHENOL_FFQ_ID = 10003
127+
SKIN_SCORING_APP_ID = 10005
127128
SPAIN_FFQ_ID = 10004
128129

129130
SYSTEM_MSG_DICTIONARY = {
@@ -398,6 +399,21 @@ class Source:
398399
'est_minutes': '30',
399400
'icon': 'survey_external.svg'
400401
},
402+
SKIN_SCORING_APP_ID: {
403+
'description': 'This will direct you to the ModiFace skin-scoring web'
404+
' app. This app allows you to upload a selfie photo, '
405+
'which will be used to generate anonymized data about '
406+
'your skin for researchers and provide you with what '
407+
'the algorithm assesses to be your top two skin '
408+
'concerns. You will be provided a username and study'
409+
' code on the next screen to access the app, which '
410+
'will link your ModiFace results to your skin sample. '
411+
'This app is hosted by a third-party provider; we are '
412+
'unable to provide any assistance if you encounter '
413+
'errors or issues while using the app.',
414+
'est_minutes': '5',
415+
'icon': 'survey_external.svg'
416+
},
401417
}
402418
LOCAL_SURVEY_SEQUENCE = [
403419
BASIC_INFO_ID,
@@ -466,7 +482,8 @@ def _get_req_survey_templates_by_source_type(source_type):
466482

467483
def _get_opt_survey_templates_by_source_type(source_type):
468484
if source_type == Source.SOURCE_TYPE_HUMAN:
469-
return [3, 4, 5, 7, MYFOODREPO_ID, POLYPHENOL_FFQ_ID, SPAIN_FFQ_ID]
485+
return [3, 4, 5, 7, MYFOODREPO_ID, POLYPHENOL_FFQ_ID,
486+
SPAIN_FFQ_ID, SKIN_SCORING_APP_ID]
470487
elif source_type == Source.SOURCE_TYPE_ANIMAL:
471488
return []
472489
elif source_type == Source.SOURCE_TYPE_ENVIRONMENT:
@@ -1398,6 +1415,14 @@ def get_fill_source_survey(*,
13981415
account_id, source_id, "data", reconsent=True
13991416
)
14001417

1418+
# this is remote, so go to an external url, not our jinja2 template
1419+
return redirect(survey_output['survey_template_text']['url'])
1420+
elif survey_template_id == SKIN_SCORING_APP_ID:
1421+
if need_reconsent:
1422+
return render_consent_page(
1423+
account_id, source_id, "data", reconsent=True
1424+
)
1425+
14011426
# this is remote, so go to an external url, not our jinja2 template
14021427
return redirect(survey_output['survey_template_text']['url'])
14031428
else:
@@ -1542,6 +1567,25 @@ def get_myfoodrepo_no_slots(*, account_id=None, source_id=None):
15421567
source_id=source_id)
15431568

15441569

1570+
@prerequisite([SOURCE_PREREQS_MET, BIOSPECIMEN_PREREQS_MET])
1571+
def post_ajax_skin_scoring_app_credentials(*, account_id, source_id):
1572+
need_reconsent = check_current_consent(account_id, source_id, "data")
1573+
1574+
if need_reconsent:
1575+
return render_consent_page(
1576+
account_id, source_id, "data", reconsent=True
1577+
)
1578+
1579+
has_error, credentials, _ = ApiRequest.post(
1580+
"/accounts/%s/sources/%s/surveys/skin_scoring_app_credentials"
1581+
% (account_id, source_id)
1582+
)
1583+
if has_error == 404:
1584+
return flask.jsonify({"app_username": "", "app_studycode": ""})
1585+
else:
1586+
return flask.jsonify(credentials)
1587+
1588+
15451589
@prerequisite([SOURCE_PREREQS_MET, BIOSPECIMEN_PREREQS_MET])
15461590
def get_fill_vioscreen_remote_sample_survey(*,
15471591
account_id=None,
@@ -1850,7 +1894,10 @@ def get_source(*, account_id=None, source_id=None):
18501894
for answer in survey_answers:
18511895
template_id = answer['survey_template_id']
18521896
for template in local_surveys + remote_surveys:
1853-
if template['survey_template_id'] == template_id:
1897+
if template['survey_template_id'] == SKIN_SCORING_APP_ID:
1898+
template['survey_id'] = answer['survey_id']
1899+
template['answered'] = True
1900+
else:
18541901
template['answered'] = True
18551902

18561903
for template in local_surveys:
@@ -1879,11 +1926,24 @@ def get_source(*, account_id=None, source_id=None):
18791926
template['est_minutes'] = SURVEY_INFO[template_id]['est_minutes']
18801927
template['icon'] = SURVEY_INFO[template_id]['icon']
18811928

1882-
# TODO: MyFoodRepo logic needs to be refactored when we reactivate it
1883-
"""
18841929
# any survey specific stuff like opening a tab
18851930
# or slot checking
1886-
for idx, template in enumerate(remote_surveys[:]):
1931+
# NB: change "_" back to "idx" when MyFoodRepo is reactivated or if
1932+
# another external survey requires similar functionality
1933+
for _, template in enumerate(remote_surveys[:]):
1934+
if template['survey_template_id'] == SKIN_SCORING_APP_ID:
1935+
has_error, credentials, _ = ApiRequest.get(
1936+
'/accounts/%s/sources/%s/surveys/skin_scoring_app_credentials'
1937+
% (account_id, source_id)
1938+
)
1939+
1940+
if has_error:
1941+
return has_error
1942+
1943+
template['credentials'] = credentials
1944+
1945+
# TODO: MyFoodRepo logic needs to be refactored when we reactivate it
1946+
"""
18871947
if template['survey_template_id'] == MYFOODREPO_ID:
18881948
has_error, slots, _ = ApiRequest.get('/slots/myfoodrepo')
18891949
if has_error:
@@ -1896,7 +1956,7 @@ def get_source(*, account_id=None, source_id=None):
18961956
per_source_not_taken.pop(idx)
18971957
else:
18981958
template['new_tab'] = False
1899-
"""
1959+
"""
19001960

19011961
local_surveys = [translate_survey_template(s) for s in local_surveys]
19021962
remote_surveys = [translate_survey_template(s) for s in remote_surveys]
@@ -1911,7 +1971,8 @@ def get_source(*, account_id=None, source_id=None):
19111971
source_name=source_output['source_name'],
19121972
profile_has_samples=profile_has_samples,
19131973
need_reconsent=need_reconsent,
1914-
show_update_age=show_update_age
1974+
show_update_age=show_update_age,
1975+
SKIN_SCORING_APP_ID=SKIN_SCORING_APP_ID
19151976
)
19161977

19171978

microsetta_interface/routes.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -835,6 +835,22 @@ paths:
835835
'302':
836836
description: Redirecting to necessary action
837837

838+
'/accounts/{account_id}/sources/{source_id}/create_skin_scoring_app_credentials':
839+
post:
840+
operationId: microsetta_interface.implementation.post_ajax_skin_scoring_app_credentials
841+
tags:
842+
- Survey
843+
parameters:
844+
- $ref: '#/components/parameters/account_id'
845+
- $ref: '#/components/parameters/source_id'
846+
responses:
847+
'200':
848+
description: Credentials for skin scoring app
849+
content:
850+
application/json:
851+
schema:
852+
type: object
853+
838854
'/accounts/{account_id}/sources/{source_id}/vioscreen_ffq':
839855
get:
840856
operationId: microsetta_interface.implementation.get_fill_vioscreen_remote_sample_survey

microsetta_interface/templates/source.jinja2

Lines changed: 102 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,74 @@
4545
}
4646
{% endif %}
4747
48+
let skin_app_username = '';
49+
let skin_app_studycode = '';
50+
51+
function handleSkinAppModal(survey_link, survey_title, survey_description, app_username, app_studycode) {
52+
$("#skin-app-modal-title").empty();
53+
$("#skin-app-modal-title").text(survey_title);
54+
$("#skin-app-survey-description").empty();
55+
$("#skin-app-survey-description").text(survey_description);
56+
57+
// We allow the function to update the global variables, which will happen when the user receives credentials and re-renders the modal.
58+
// However, there's no situation where we'd retract the credentials, so we don't need to do anything with blank values.
59+
if(app_username != '' && app_username != 'None') {
60+
skin_app_username = app_username
61+
skin_app_studycode = app_studycode
62+
}
63+
64+
if(skin_app_username != '') {
65+
$("#skin-app-take-survey").html('{{ _('Take This Survey Now') }}');
66+
67+
document.getElementById("app-credentials-div").style.display = '';
68+
document.getElementById("app-credentials-username").innerHTML = skin_app_username;
69+
document.getElementById("app-credentials-studycode").innerHTML = skin_app_studycode;
70+
71+
// Once we display the credentials, the button action is to open the actual survey
72+
$("#skin-app-take-survey").click(function(e) {
73+
window.open(survey_link, '_blank').focus();
74+
});
75+
} else {
76+
$("#skin-app-take-survey").html('{{ _('Get Username and Study Code') }}');
77+
78+
document.getElementById("app-credentials-div").style.display = 'none';
79+
$("#skin-app-take-survey").click(async function(e) {
80+
let url = '/accounts/{{ account_id }}/sources/{{ source_id }}/create_skin_scoring_app_credentials';
81+
$.ajax({
82+
url: url,
83+
type: "POST",
84+
success: function(data) {
85+
if(!Object.hasOwn(data, 'app_username')) {
86+
// The response doesn't have credentials, something went very wrong. Handle it gracefully.
87+
document.getElementById("app-error").style.display = '';
88+
} else {
89+
let s_user = data['app_username'];
90+
let s_sc = data['app_studycode'];
91+
if (s_user.length < 1) {
92+
// We weren't able to allocate them credentials.
93+
document.getElementById("app-error").style.display = '';
94+
} else {
95+
// We got credentials, refresh this modal
96+
handleSkinAppModal(survey_link, survey_title, survey_description, s_user, s_sc);
97+
}
98+
}
99+
}
100+
});
101+
});
102+
}
103+
$("#skin_app_modal").modal('show');
104+
return false;
105+
}
106+
48107
function hideModal() {
49108
$("#source_modal").modal('hide');
50109
return false;
51110
}
111+
112+
function hideSkinModal() {
113+
$("#skin_app_modal").modal('hide');
114+
return false;
115+
}
52116
</script>
53117
<script src="/static/vendor/js/jquery.form-4.2.2/jquery.form.min.js"></script>
54118
{% endblock %}
@@ -123,8 +187,12 @@
123187
<div class="row mt-4">
124188
{% for detail in remote_surveys %}
125189
<div class="col-12 col-sm-6 col-md-4 p-2">
126-
{% if not detail.answered %}
127-
<a style="text-decoration: none" onClick="return takeExternalSurvey('/accounts/{{ account_id }}/sources/{{ source_id }}/take_survey?survey_template_id={{ detail.survey_template_id }}', '{{ detail.survey_template_title }}', '{{ detail.description }}');" href="/accounts/{{ account_id }}/sources/{{ source_id }}/take_survey?survey_template_id={{ detail.survey_template_id }}" target="_blank">
190+
{% if detail.survey_template_id == SKIN_SCORING_APP_ID %}
191+
<a style="text-decoration: none" onClick="return handleSkinAppModal('/accounts/{{ account_id }}/sources/{{ source_id }}/take_survey?survey_template_id={{ detail.survey_template_id }}', '{{ detail.survey_template_title }}', '{{ detail.description }}', '{{ detail.credentials['app_username'] }}', '{{ detail.credentials['app_studycode'] }}');" href="/accounts/{{ account_id }}/sources/{{ source_id }}/take_survey?survey_template_id={{ detail.survey_template_id }}" target="_blank">
192+
{% else %}
193+
{% if not detail.answered %}
194+
<a style="text-decoration: none" onClick="return takeExternalSurvey('/accounts/{{ account_id }}/sources/{{ source_id }}/take_survey?survey_template_id={{ detail.survey_template_id }}', '{{ detail.survey_template_title }}', '{{ detail.description }}');" href="/accounts/{{ account_id }}/sources/{{ source_id }}/take_survey?survey_template_id={{ detail.survey_template_id }}" target="_blank">
195+
{% endif %}
128196
{% endif %}
129197
<div class="card card-survey-external">
130198
<div class="row m-2 survey-info-row">
@@ -136,7 +204,7 @@
136204
<div class="col-4 small-text survey-info text-end">{{ detail.est_minutes }} {{ _('min') }}</div>
137205
</div>
138206
<div class="row m-3 text-center survey-title">
139-
{% if not detail.answered %}
207+
{% if not detail.answered or detail.survey_template_id == SKIN_SCORING_APP_ID %}
140208
<img src="/static/img/survey_external.svg" class="card-survey-icon-external">
141209
{% else %}
142210
<img src="/static/img/survey_external_taken.svg" class="card-survey-icon-external">
@@ -150,7 +218,7 @@
150218
{% endif %}
151219
</div>
152220
</div>
153-
{% if not detail.answered %}
221+
{% if not detail.answered or detail.survey_template_id == SKIN_SCORING_APP_ID %}
154222
</a>
155223
{% endif %}
156224
</div>
@@ -179,4 +247,34 @@
179247
</div>
180248
</div>
181249

250+
<! -- Modal for skin-scoring app -->
251+
<div class="modal fade" id="skin_app_modal" tabindex="-1" role="dialog" aria-labelledby="skinAppModal" aria-hidden="true">
252+
<div class="modal-dialog" role="document">
253+
<div class="modal-content">
254+
<div class="modal-header">
255+
<img src="/static/img/survey_external.svg">
256+
<span class="survey-title" id="skin-app-modal-title"></span>
257+
</div>
258+
<div class="modal-body">
259+
<p class="survey-description" id="skin-app-survey-description"></p>
260+
</div>
261+
<div class="modal-body" id="app-credentials-div">
262+
<p class="survey-description">
263+
<strong>Username: </strong><span id="app-credentials-username"></span><br />
264+
<strong>Study Code: </strong><span id="app-credentials-studycode"></span>
265+
</p>
266+
</div>
267+
<div class="modal-body" id="app-error" style="display: none">
268+
<p class="survey-description">
269+
{{ _('Sorry, there was a problem generating a username and study code. Please try again later.') }}
270+
</p>
271+
</div>
272+
<div class="modal-footer">
273+
<button type="button" class="btn btn-white-blue-border" id="skin_app_cancel" onClick="hideSkinModal();">{{ _('Maybe Later') }}</button>
274+
<button type="button" class="btn btn-blue-gradient" id="skin-app-take-survey">{{ _('Take This Survey Now') }}</button>
275+
</div>
276+
</div>
277+
</div>
278+
</div>
279+
182280
{% endblock %}

microsetta_interface/tests/test_integration.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ def _fake_jwt(email, verified, uniqify=False):
106106
MYFOODREPO_ID = 10002
107107
POLYPHENOL_FFQ_ID = 10003
108108
SPAIN_FFQ_ID = 10004
109+
SKIN_SCORING_APP_ID = 10005
109110

110111
BASIC_INFO_SIMPLE = {"112": "1970"}
111112
BASIC_INFO_SIMPLE_ALT = {"112": "1983"}
@@ -573,6 +574,11 @@ def _complete_spain_ffq_survey(self, account_id, source_id):
573574
f'take_survey?survey_template_id=10004')
574575
return self.app.get(url), url
575576

577+
def _complete_skin_scoring_app_survey(self, account_id, source_id):
578+
url = (f'/accounts/{account_id}/sources/{source_id}/'
579+
f'take_survey?survey_template_id={SKIN_SCORING_APP_ID}')
580+
return self.app.get(url), url
581+
576582
def test_new_user_to_source_listing(self):
577583
resp, url, user_jwt = self._new_to_create()
578584
account_id, _, _ = self._ids_from_url(url)

0 commit comments

Comments
 (0)