diff --git a/microsetta_interface/implementation.py b/microsetta_interface/implementation.py index bfa7a1c6..78efbfb3 100644 --- a/microsetta_interface/implementation.py +++ b/microsetta_interface/implementation.py @@ -124,6 +124,7 @@ class Source: VIOSCREEN_ID = 10001 MYFOODREPO_ID = 10002 POLYPHENOL_FFQ_ID = 10003 +SKIN_SCORING_APP_ID = 10005 SPAIN_FFQ_ID = 10004 SYSTEM_MSG_DICTIONARY = { @@ -398,6 +399,21 @@ class Source: 'est_minutes': '30', 'icon': 'survey_external.svg' }, + SKIN_SCORING_APP_ID: { + 'description': 'This will direct you to the ModiFace skin-scoring web' + ' app. This app allows you to upload a selfie photo, ' + 'which will be used to generate anonymized data about ' + 'your skin for researchers and provide you with what ' + 'the algorithm assesses to be your top two skin ' + 'concerns. You will be provided a username and study' + ' code on the next screen to access the app, which ' + 'will link your ModiFace results to your skin sample. ' + 'This app is hosted by a third-party provider; we are ' + 'unable to provide any assistance if you encounter ' + 'errors or issues while using the app.', + 'est_minutes': '5', + 'icon': 'survey_external.svg' + }, } LOCAL_SURVEY_SEQUENCE = [ BASIC_INFO_ID, @@ -466,7 +482,8 @@ def _get_req_survey_templates_by_source_type(source_type): def _get_opt_survey_templates_by_source_type(source_type): if source_type == Source.SOURCE_TYPE_HUMAN: - return [3, 4, 5, 7, MYFOODREPO_ID, POLYPHENOL_FFQ_ID, SPAIN_FFQ_ID] + return [3, 4, 5, 7, MYFOODREPO_ID, POLYPHENOL_FFQ_ID, + SPAIN_FFQ_ID, SKIN_SCORING_APP_ID] elif source_type == Source.SOURCE_TYPE_ANIMAL: return [] elif source_type == Source.SOURCE_TYPE_ENVIRONMENT: @@ -1398,6 +1415,14 @@ def get_fill_source_survey(*, account_id, source_id, "data", reconsent=True ) + # this is remote, so go to an external url, not our jinja2 template + return redirect(survey_output['survey_template_text']['url']) + elif survey_template_id == SKIN_SCORING_APP_ID: + if need_reconsent: + return render_consent_page( + account_id, source_id, "data", reconsent=True + ) + # this is remote, so go to an external url, not our jinja2 template return redirect(survey_output['survey_template_text']['url']) else: @@ -1542,6 +1567,25 @@ def get_myfoodrepo_no_slots(*, account_id=None, source_id=None): source_id=source_id) +@prerequisite([SOURCE_PREREQS_MET, BIOSPECIMEN_PREREQS_MET]) +def post_ajax_skin_scoring_app_credentials(*, account_id, source_id): + need_reconsent = check_current_consent(account_id, source_id, "data") + + if need_reconsent: + return render_consent_page( + account_id, source_id, "data", reconsent=True + ) + + has_error, credentials, _ = ApiRequest.post( + "/accounts/%s/sources/%s/surveys/skin_scoring_app_credentials" + % (account_id, source_id) + ) + if has_error == 404: + return flask.jsonify({"app_username": "", "app_studycode": ""}) + else: + return flask.jsonify(credentials) + + @prerequisite([SOURCE_PREREQS_MET, BIOSPECIMEN_PREREQS_MET]) def get_fill_vioscreen_remote_sample_survey(*, account_id=None, @@ -1850,7 +1894,10 @@ def get_source(*, account_id=None, source_id=None): for answer in survey_answers: template_id = answer['survey_template_id'] for template in local_surveys + remote_surveys: - if template['survey_template_id'] == template_id: + if template['survey_template_id'] == SKIN_SCORING_APP_ID: + template['survey_id'] = answer['survey_id'] + template['answered'] = True + else: template['answered'] = True for template in local_surveys: @@ -1879,11 +1926,24 @@ def get_source(*, account_id=None, source_id=None): template['est_minutes'] = SURVEY_INFO[template_id]['est_minutes'] template['icon'] = SURVEY_INFO[template_id]['icon'] - # TODO: MyFoodRepo logic needs to be refactored when we reactivate it - """ # any survey specific stuff like opening a tab # or slot checking - for idx, template in enumerate(remote_surveys[:]): + # NB: change "_" back to "idx" when MyFoodRepo is reactivated or if + # another external survey requires similar functionality + for _, template in enumerate(remote_surveys[:]): + if template['survey_template_id'] == SKIN_SCORING_APP_ID: + has_error, credentials, _ = ApiRequest.get( + '/accounts/%s/sources/%s/surveys/skin_scoring_app_credentials' + % (account_id, source_id) + ) + + if has_error: + return has_error + + template['credentials'] = credentials + + # TODO: MyFoodRepo logic needs to be refactored when we reactivate it + """ if template['survey_template_id'] == MYFOODREPO_ID: has_error, slots, _ = ApiRequest.get('/slots/myfoodrepo') if has_error: @@ -1896,7 +1956,7 @@ def get_source(*, account_id=None, source_id=None): per_source_not_taken.pop(idx) else: template['new_tab'] = False - """ + """ local_surveys = [translate_survey_template(s) for s in local_surveys] remote_surveys = [translate_survey_template(s) for s in remote_surveys] @@ -1911,7 +1971,8 @@ def get_source(*, account_id=None, source_id=None): source_name=source_output['source_name'], profile_has_samples=profile_has_samples, need_reconsent=need_reconsent, - show_update_age=show_update_age + show_update_age=show_update_age, + SKIN_SCORING_APP_ID=SKIN_SCORING_APP_ID ) diff --git a/microsetta_interface/routes.yaml b/microsetta_interface/routes.yaml index 75fd7842..8cad9bf9 100644 --- a/microsetta_interface/routes.yaml +++ b/microsetta_interface/routes.yaml @@ -835,6 +835,22 @@ paths: '302': description: Redirecting to necessary action + '/accounts/{account_id}/sources/{source_id}/create_skin_scoring_app_credentials': + post: + operationId: microsetta_interface.implementation.post_ajax_skin_scoring_app_credentials + tags: + - Survey + parameters: + - $ref: '#/components/parameters/account_id' + - $ref: '#/components/parameters/source_id' + responses: + '200': + description: Credentials for skin scoring app + content: + application/json: + schema: + type: object + '/accounts/{account_id}/sources/{source_id}/vioscreen_ffq': get: operationId: microsetta_interface.implementation.get_fill_vioscreen_remote_sample_survey diff --git a/microsetta_interface/templates/source.jinja2 b/microsetta_interface/templates/source.jinja2 index eafd0f76..bd36cdf3 100644 --- a/microsetta_interface/templates/source.jinja2 +++ b/microsetta_interface/templates/source.jinja2 @@ -45,10 +45,74 @@ } {% endif %} + let skin_app_username = ''; + let skin_app_studycode = ''; + + function handleSkinAppModal(survey_link, survey_title, survey_description, app_username, app_studycode) { + $("#skin-app-modal-title").empty(); + $("#skin-app-modal-title").text(survey_title); + $("#skin-app-survey-description").empty(); + $("#skin-app-survey-description").text(survey_description); + + // We allow the function to update the global variables, which will happen when the user receives credentials and re-renders the modal. + // However, there's no situation where we'd retract the credentials, so we don't need to do anything with blank values. + if(app_username != '' && app_username != 'None') { + skin_app_username = app_username + skin_app_studycode = app_studycode + } + + if(skin_app_username != '') { + $("#skin-app-take-survey").html('{{ _('Take This Survey Now') }}'); + + document.getElementById("app-credentials-div").style.display = ''; + document.getElementById("app-credentials-username").innerHTML = skin_app_username; + document.getElementById("app-credentials-studycode").innerHTML = skin_app_studycode; + + // Once we display the credentials, the button action is to open the actual survey + $("#skin-app-take-survey").click(function(e) { + window.open(survey_link, '_blank').focus(); + }); + } else { + $("#skin-app-take-survey").html('{{ _('Get Username and Study Code') }}'); + + document.getElementById("app-credentials-div").style.display = 'none'; + $("#skin-app-take-survey").click(async function(e) { + let url = '/accounts/{{ account_id }}/sources/{{ source_id }}/create_skin_scoring_app_credentials'; + $.ajax({ + url: url, + type: "POST", + success: function(data) { + if(!Object.hasOwn(data, 'app_username')) { + // The response doesn't have credentials, something went very wrong. Handle it gracefully. + document.getElementById("app-error").style.display = ''; + } else { + let s_user = data['app_username']; + let s_sc = data['app_studycode']; + if (s_user.length < 1) { + // We weren't able to allocate them credentials. + document.getElementById("app-error").style.display = ''; + } else { + // We got credentials, refresh this modal + handleSkinAppModal(survey_link, survey_title, survey_description, s_user, s_sc); + } + } + } + }); + }); + } + $("#skin_app_modal").modal('show'); + return false; + } + function hideModal() { $("#source_modal").modal('hide'); return false; } + + function hideSkinModal() { + $("#skin_app_modal").modal('hide'); + return false; + } {% endblock %} @@ -123,8 +187,12 @@
{% for detail in remote_surveys %}
- {% if not detail.answered %} - + {% if detail.survey_template_id == SKIN_SCORING_APP_ID %} + + {% else %} + {% if not detail.answered %} + + {% endif %} {% endif %}
@@ -136,7 +204,7 @@
{{ detail.est_minutes }} {{ _('min') }}
- {% if not detail.answered %} + {% if not detail.answered or detail.survey_template_id == SKIN_SCORING_APP_ID %} {% else %} @@ -150,7 +218,7 @@ {% endif %}
- {% if not detail.answered %} + {% if not detail.answered or detail.survey_template_id == SKIN_SCORING_APP_ID %}
{% endif %}
@@ -179,4 +247,34 @@
+ + + {% endblock %} diff --git a/microsetta_interface/tests/test_integration.py b/microsetta_interface/tests/test_integration.py index 375df209..942b96aa 100644 --- a/microsetta_interface/tests/test_integration.py +++ b/microsetta_interface/tests/test_integration.py @@ -106,6 +106,7 @@ def _fake_jwt(email, verified, uniqify=False): MYFOODREPO_ID = 10002 POLYPHENOL_FFQ_ID = 10003 SPAIN_FFQ_ID = 10004 +SKIN_SCORING_APP_ID = 10005 BASIC_INFO_SIMPLE = {"112": "1970"} BASIC_INFO_SIMPLE_ALT = {"112": "1983"} @@ -573,6 +574,11 @@ def _complete_spain_ffq_survey(self, account_id, source_id): f'take_survey?survey_template_id=10004') return self.app.get(url), url + def _complete_skin_scoring_app_survey(self, account_id, source_id): + url = (f'/accounts/{account_id}/sources/{source_id}/' + f'take_survey?survey_template_id={SKIN_SCORING_APP_ID}') + return self.app.get(url), url + def test_new_user_to_source_listing(self): resp, url, user_jwt = self._new_to_create() account_id, _, _ = self._ids_from_url(url)