Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

external survey - skin scoring app #583

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 60 additions & 20 deletions microsetta_private_api/api/_survey.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from microsetta_private_api.repo.transaction import Transaction
from microsetta_private_api.repo.vioscreen_repo import VioscreenRepo
from microsetta_private_api.util import vioscreen, myfoodrepo, vue_adapter, \
polyphenol_ffq
polyphenol_ffq, skin_scoring_app
from microsetta_private_api.util.vioscreen import VioscreenAdminAPI
from microsetta_private_api.config_manager import SERVER_CONFIG

Expand All @@ -34,31 +34,46 @@ def read_survey_templates(account_id, source_id, language_tag, token_info):
with Transaction() as t:
source_repo = SourceRepo(t)
source = source_repo.get_source(account_id, source_id)

if source is None:
return jsonify(code=404, message="No source found"), 404

template_repo = SurveyTemplateRepo(t)

if source.source_type == Source.SOURCE_TYPE_HUMAN:
return jsonify([template_repo.get_survey_template_link_info(x)
for x in [
SurveyTemplateRepo.VIOSCREEN_ID,
SurveyTemplateRepo.POLYPHENOL_FFQ_ID,
SurveyTemplateRepo.SPAIN_FFQ_ID,
SurveyTemplateRepo.BASIC_INFO_ID,
SurveyTemplateRepo.AT_HOME_ID,
SurveyTemplateRepo.LIFESTYLE_ID,
SurveyTemplateRepo.GUT_ID,
SurveyTemplateRepo.GENERAL_HEALTH_ID,
SurveyTemplateRepo.HEALTH_DIAG_ID,
SurveyTemplateRepo.ALLERGIES_ID,
SurveyTemplateRepo.DIET_ID,
SurveyTemplateRepo.DETAILED_DIET_ID,
SurveyTemplateRepo.OTHER_ID
]]), 200
# Checking samples to see if any have
# permission to see Skin Scoring App survey
sample_repo = SampleRepo(t)
samples = sample_repo.get_samples_by_source(account_id, source_id)
has_skin_sample = any(
s.project_id == SurveyTemplateRepo.SBI_PROJECT_ID
for s in samples) if samples else False

template_ids = [
SurveyTemplateRepo.VIOSCREEN_ID,
SurveyTemplateRepo.POLYPHENOL_FFQ_ID,
SurveyTemplateRepo.SPAIN_FFQ_ID,
SurveyTemplateRepo.BASIC_INFO_ID,
SurveyTemplateRepo.AT_HOME_ID,
SurveyTemplateRepo.LIFESTYLE_ID,
SurveyTemplateRepo.GUT_ID,
SurveyTemplateRepo.GENERAL_HEALTH_ID,
SurveyTemplateRepo.HEALTH_DIAG_ID,
SurveyTemplateRepo.ALLERGIES_ID,
SurveyTemplateRepo.DIET_ID,
SurveyTemplateRepo.DETAILED_DIET_ID,
SurveyTemplateRepo.OTHER_ID
]
if has_skin_sample:
template_ids.append(SurveyTemplateRepo.SKIN_SCORING_APP_ID)

elif source.source_type == Source.SOURCE_TYPE_ANIMAL:
return jsonify([template_repo.get_survey_template_link_info(x)
for x in [2]]), 200
template_ids = [2]
else:
return jsonify([]), 200
template_ids = []

return jsonify([template_repo.get_survey_template_link_info(x)
for x in template_ids]), 200


def _remote_survey_url_vioscreen(transaction, account_id, source_id,
Expand Down Expand Up @@ -181,6 +196,25 @@ def _remote_survey_url_spain_ffq(transaction, account_id, source_id):
return SERVER_CONFIG['spain_ffq_url']


def _remote_survey_url_skin_scoring_app(transaction,
account_id,
source_id,
language_tag):
st_repo = SurveyTemplateRepo(transaction)

skin_scoring_app_id = \
st_repo.get_skin_scoring_app_id_if_exists(account_id,
source_id)

if skin_scoring_app_id is None:
skin_scoring_app_id = \
st_repo.create_skin_scoring_app_entry(account_id,
source_id,
language_tag)
return skin_scoring_app.gen_url(skin_scoring_app_id,
language_tag)


def read_survey_template(account_id, source_id, survey_template_id,
language_tag, token_info, survey_redirect_url=None,
vioscreen_ext_sample_id=None,
Expand Down Expand Up @@ -220,6 +254,12 @@ def read_survey_template(account_id, source_id, survey_template_id,
url = _remote_survey_url_spain_ffq(t,
account_id,
source_id)
elif survey_template_id == \
SurveyTemplateRepo.SKIN_SCORING_APP_ID:
url = _remote_survey_url_skin_scoring_app(t,
account_id,
source_id,
language_tag)
else:
raise ValueError(f"Cannot generate URL for survey "
f"{survey_template_id}")
Expand Down
2 changes: 2 additions & 0 deletions microsetta_private_api/api/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,8 @@ def delete_dummy_accts():
template_repo.delete_polyphenol_ffq(curr_acct_id,
curr_source.id)
template_repo.delete_spain_ffq(curr_acct_id, curr_source.id)
template_repo.delete_skin_scoring_app(curr_acct_id,
curr_source.id)

# Dissociate all samples linked to this source from all
# answered surveys linked to this source, then delete all
Expand Down
36 changes: 36 additions & 0 deletions microsetta_private_api/api/tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,42 @@ def test_bobo_takes_polyphenol_ffq(self):
)
check_response(resp, 404)

@skipIf(SERVER_CONFIG['skin_scoring_app_url'] in
('', 'ssa_placeholder'),
"Skin Scoring App secrets not provided")
def test_bobo_takes_skin_scoring_app(self):
bobo = self._bobo_to_claim_a_sample()

# take Skin Scoring App
resp = self.client.get(
'/api/accounts/%s/sources/%s/survey_templates/10005'
'?language_tag=en_US' %
(ACCT_ID, bobo['source_id']),
headers=MOCK_HEADERS
)
check_response(resp)
data = json.loads(resp.data)
exp_start = SERVER_CONFIG['skin_scoring_app_url']
url = data['survey_template_text']['url']
self.assertTrue(url.startswith(exp_start))

# verify we err if we attempt to answer the survey. an "answer" here is
# undefined
resp = self.client.post(
'/api/accounts/%s/sources/%s/surveys'
'?language_tag=en_US' %
(ACCT_ID, bobo['source_id']),
content_type='application/json',
data=json.dumps(
{
"survey_template_id":
SurveyTemplateRepo.SKIN_SCORING_APP_ID,
"survey_text": {'key': 'stuff'}
}),
headers=MOCK_HEADERS
)
check_response(resp, 404)

@skipIf(SERVER_CONFIG['spain_ffq_url'] in ('', 'sffq_placeholder'),
"Spain FFQ secrets not provided")
def test_bobo_takes_spain_ffq(self):
Expand Down
13 changes: 13 additions & 0 deletions microsetta_private_api/db/patches/0143.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
CREATE TABLE ag.skin_scoring_app_registry (
skin_scoring_app_id VARCHAR PRIMARY KEY,
account_id UUID NOT NULL,
source_id UUID,
language_tag VARCHAR,
deleted BOOLEAN NOT NULL DEFAULT false,
creation_timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),

CONSTRAINT fk_skin_scoring_app_registry_account FOREIGN KEY (account_id) REFERENCES ag.account(id),
CONSTRAINT fk_skin_scoring_app_registry_source FOREIGN KEY (source_id) REFERENCES ag.source(id)
);

CREATE INDEX skin_scoring_app_registry_source ON ag.skin_scoring_app_registry (account_id, source_id);
14 changes: 14 additions & 0 deletions microsetta_private_api/repo/sample_repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,9 @@ def get_samples_by_source(self, account_id, source_id,
sample.kit_id = self._get_supplied_kit_id_by_sample(
sample.barcode
)
sample.project_id = self._get_project_id_by_sample(
sample.barcode
)
samples.append(sample)
return samples

Expand Down Expand Up @@ -404,6 +407,17 @@ def _get_supplied_kit_id_by_sample(self, sample_barcode):
row = cur.fetchone()
return row[0]

def _get_project_id_by_sample(self, sample_barcode):
with self._transaction.cursor() as cur:
cur.execute(
"SELECT project_id "
"FROM barcodes.project_barcode "
"WHERE barcode = %s",
(sample_barcode, )
)
row = cur.fetchone()
ayobi marked this conversation as resolved.
Show resolved Hide resolved
return row[0]

def scrub(self, account_id, source_id, sample_id):
"""Wipe out free text information for a sample

Expand Down
136 changes: 135 additions & 1 deletion microsetta_private_api/repo/survey_template_repo.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import random
import string
import psycopg2
from werkzeug.exceptions import NotFound

from microsetta_private_api.config_manager import SERVER_CONFIG
Expand All @@ -23,6 +26,7 @@ class SurveyTemplateRepo(BaseRepo):
MYFOODREPO_ID = 10002
POLYPHENOL_FFQ_ID = 10003
SPAIN_FFQ_ID = 10004
SKIN_SCORING_APP_ID = 10005
BASIC_INFO_ID = 10
AT_HOME_ID = 11
LIFESTYLE_ID = 12
Expand All @@ -36,6 +40,7 @@ class SurveyTemplateRepo(BaseRepo):
SURFERS_ID = 20
COVID19_ID = 21
OTHER_ID = 22
SBI_PROJECT_ID = 58

SURVEY_INFO = {
# For now, let's keep legacy survey info as well.
Expand Down Expand Up @@ -105,6 +110,12 @@ class SurveyTemplateRepo(BaseRepo):
"1.0",
"remote"
),
SKIN_SCORING_APP_ID: SurveyTemplateLinkInfo(
SKIN_SCORING_APP_ID,
"Skin Scoring App",
"1.0",
"remote"
),
BASIC_INFO_ID: SurveyTemplateLinkInfo(
BASIC_INFO_ID,
"Basic Information",
Expand Down Expand Up @@ -755,6 +766,127 @@ def delete_spain_ffq(self, account_id, source_id):
AND source_id=%s""",
(account_id, source_id))

def create_skin_scoring_app_entry(self,
account_id,
source_id,
language_tag,):
"""Return a newly created Skin Scoring App ID

Parameters
----------
account_id : str, UUID
The account UUID
source_id : str, UUID
The source UUID
language_tag: str
The user's language tag

Returns
-------
str
The newly created Skin Scoring App ID
"""
characters = string.ascii_lowercase + string.digits

for attempt in range(5):
ayobi marked this conversation as resolved.
Show resolved Hide resolved
skin_scoring_app_id = ''.join(random.choices(characters, k=8))

try:
with self._transaction.cursor() as cur:
cur.execute("""INSERT INTO ag.skin_scoring_app_registry
(skin_scoring_app_id, account_id,
source_id, language_tag)
VALUES (%s, %s, %s, %s)""",
(skin_scoring_app_id, account_id,
source_id, language_tag))

# Put a survey into ag_login_surveys
cur.execute("INSERT INTO ag_login_surveys("
"ag_login_id, "
"survey_id, "
"vioscreen_status, "
"source_id, "
"survey_template_id) "
"VALUES(%s, %s, %s, %s, %s)",
(account_id, skin_scoring_app_id, None,
source_id,
SurveyTemplateRepo.SKIN_SCORING_APP_ID))

return skin_scoring_app_id
except psycopg2.IntegrityError as e:
self._transaction.rollback()

if e.pgcode == '23505':
print(f"Duplicate ID '{skin_scoring_app_id}' "
f"detected. Retrying... (Attempt {attempt + 1}/{5})")
ayobi marked this conversation as resolved.
Show resolved Hide resolved
else:
raise

raise Exception("Unable to generate a unique "
"Skin Scoring App ID after 5 attempts")

def get_skin_scoring_app_id_if_exists(self,
account_id,
source_id):
"""Return a Skin Scoring App ID if one exists

Parameters
----------
account_id : str, UUID
The account UUID
source_id : str, UUID
The source UUID

Returns
-------
(str) or (None)
The associated Skin Scoring App ID
It's impossible to find one without the other
"""
with self._transaction.cursor() as cur:
cur.execute("""SELECT skin_scoring_app_id
FROM ag.skin_scoring_app_registry
WHERE account_id=%s AND source_id=%s""",
(account_id, source_id))
res = cur.fetchone()

if res is None:
return None
else:
return res

def delete_skin_scoring_app(self, account_id, source_id):
"""Intended for admin use, remove Skin Scoring App entries

This method is idempotent.

This method deletes ALL Skin Scoring App surveys associated with an
account and source

This is a hard delete, we REMOVE rows rather than setting a flag

Parameters
----------
account_id : str, UUID
The account UUID
source_id : str, UUID
The source UUID
"""
with self._transaction.cursor() as cur:
existing = \
self.get_skin_scoring_app_id_if_exists(account_id,
source_id)
if existing is not None:
cur.execute("""DELETE FROM ag.ag_login_surveys
WHERE ag_login_id=%s
AND source_id=%s
AND survey_id=%s""",
(account_id, source_id, existing))
cur.execute("""DELETE FROM ag.skin_scoring_app_registry
WHERE account_id=%s
AND source_id=%s""",
(account_id, source_id))

ayobi marked this conversation as resolved.
Show resolved Hide resolved
def get_vioscreen_sample_to_user(self):
"""Obtain a mapping of sample barcode to vioscreen user"""
with self._transaction.cursor() as cur:
Expand Down Expand Up @@ -1127,6 +1259,7 @@ def has_external_surveys(self, account_id, source_id):
getters = (self.get_myfoodrepo_id_if_exists,
self.get_polyphenol_ffq_id_if_exists,
self.get_spain_ffq_id_if_exists,
self.get_skin_scoring_app_id_if_exists,
self.get_vioscreen_all_ids_if_exists)

for get in getters:
Expand Down Expand Up @@ -1306,7 +1439,8 @@ def _generate_empty_survey(self, survey_template_id, return_tuple=False):
if survey_template_id in [self.VIOSCREEN_ID,
self.MYFOODREPO_ID,
self.POLYPHENOL_FFQ_ID,
self.SPAIN_FFQ_ID]:
self.SPAIN_FFQ_ID,
self.SKIN_SCORING_APP_ID]:
raise ValueError("survey_template_id must be for a local "
"survey")
else:
Expand Down
1 change: 1 addition & 0 deletions microsetta_private_api/server_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"fundrazr_organization": "fundrazr_org_placeholder",
"polyphenol_ffq_url": "pffq_placeholder",
"spain_ffq_url": "sffq_placeholder",
"skin_scoring_app_url": "ssa_placeholder",
"fulfillment_account_id": "000fc4cd-8fa4-db8b-e050-8a800c5d81b7",
"google_geocoding_url": "https://maps.googleapis.com/maps/api/geocode/json",
"google_geocoding_key": "geocoding_key_placeholder",
Expand Down
Loading
Loading