Skip to content

Commit 8223ff5

Browse files
authored
Merge branch 'biocore:master' into japan_locale_change
2 parents 63c6c57 + dd07e0a commit 8223ff5

15 files changed

+281
-55
lines changed

microsetta_private_api/admin/admin_impl.py

+10-4
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,7 @@ def query_project_barcode_stats(body, token_info, strip_sampleid):
491491
def query_barcode_stats(body, token_info, strip_sampleid):
492492
validate_admin_access(token_info)
493493
if 'sample_barcodes' in body:
494+
project_id = None
494495
barcodes = body["sample_barcodes"]
495496
elif 'project_id' in body:
496497
project_id = body["project_id"]
@@ -502,7 +503,7 @@ def query_barcode_stats(body, token_info, strip_sampleid):
502503
unprocessed_barcodes = barcodes[1000:]
503504
barcodes = barcodes[0:1000]
504505

505-
results = {'samples': per_sample(None, barcodes, strip_sampleid)}
506+
results = {'samples': per_sample(project_id, barcodes, strip_sampleid)}
506507

507508
if unprocessed_barcodes:
508509
results['partial_result'] = True
@@ -835,6 +836,7 @@ def delete_account(account_id, token_info):
835836
src_repo = SourceRepo(t)
836837
samp_repo = SampleRepo(t)
837838
sar_repo = SurveyAnswersRepo(t)
839+
interested_users_repo = InterestedUserRepo(t)
838840

839841
acct = acct_repo.get_account(account_id)
840842
if acct is None:
@@ -872,6 +874,8 @@ def delete_account(account_id, token_info):
872874

873875
acct_repo.scrub(account_id)
874876

877+
interested_users_repo.scrub(acct.email)
878+
875879
t.commit()
876880

877881
return None, 204
@@ -885,15 +889,16 @@ def ignore_removal_request(account_id, token_info):
885889
try:
886890
# remove the user from the queue, noting the admin who allowed it
887891
# and the time the action was performed.
888-
rq_repo.update_queue(account_id, token_info['email'], 'ignored')
892+
rq_repo.update_queue(account_id, token_info['email'],
893+
'ignored', None)
889894
t.commit()
890895
except RepoException as e:
891896
raise e
892897

893898
return None, 204
894899

895900

896-
def allow_removal_request(account_id, token_info):
901+
def allow_removal_request(account_id, token_info, delete_reason):
897902
validate_admin_access(token_info)
898903

899904
with Transaction() as t:
@@ -902,7 +907,8 @@ def allow_removal_request(account_id, token_info):
902907
try:
903908
# remove the user from the queue, noting the admin who allowed it
904909
# and the time the action was performed.
905-
rq_repo.update_queue(account_id, token_info['email'], 'deleted')
910+
rq_repo.update_queue(account_id, token_info['email'],
911+
'deleted', delete_reason)
906912
t.commit()
907913
except RepoException as e:
908914
raise e

microsetta_private_api/admin/sample_summary.py

+11-7
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,12 @@ def per_sample(project, barcodes, strip_sampleid):
2323
template_repo = SurveyTemplateRepo(t)
2424
vs_repo = VioscreenSessionRepo(t)
2525

26-
if project is not None:
27-
project_barcodes = admin_repo.get_project_barcodes(project)
28-
else:
29-
project = 'Unspecified'
30-
26+
# all associated projects returned for each barcode,
27+
# so no universal project needed
3128
if barcodes is None:
32-
barcodes = project_barcodes
29+
if project is None:
30+
return summaries
31+
barcodes = admin_repo.get_project_barcodes(project)
3332

3433
for barcode in barcodes:
3534
diag = admin_repo.retrieve_diagnostics_by_barcode(barcode)
@@ -44,6 +43,11 @@ def per_sample(project, barcodes, strip_sampleid):
4443
source_type = None if source is None else source.source_type
4544
vio_id = None
4645

46+
# find all projects for barcode
47+
projects_info = diag['projects_info']
48+
all_projects = [proj_obj['project'] for proj_obj in projects_info]
49+
barcode_project = '; '.join(sorted(all_projects))
50+
4751
if source is not None and source_type == Source.SOURCE_TYPE_HUMAN:
4852

4953
vio_id = template_repo.get_vioscreen_id_if_exists(account.id,
@@ -82,7 +86,7 @@ def per_sample(project, barcodes, strip_sampleid):
8286

8387
summary = {
8488
"sampleid": None if strip_sampleid else barcode,
85-
"project": project,
89+
"project": barcode_project,
8690
"source-type": source_type,
8791
"site-sampled": sample_site,
8892
"sample-date": sample_date,

microsetta_private_api/admin/tests/test_admin_api.py

+51
Original file line numberDiff line numberDiff line change
@@ -1252,6 +1252,57 @@ def test_query_barcode_stats_project_barcodes_with_strip(self):
12521252
self.assertEqual([v['sample-status'] for v in response_obj],
12531253
exp_status)
12541254

1255+
def test_query_barcode_stats_multiple_projects(self):
1256+
barcodes = ['000005059', '000005078', '000005103']
1257+
input_json = json.dumps({'sample_barcodes': barcodes})
1258+
1259+
response = self.client.post(
1260+
"api/admin/account_barcode_summary?strip_sampleid=False",
1261+
content_type='application/json',
1262+
data=input_json,
1263+
headers=MOCK_HEADERS
1264+
)
1265+
# an empty string project should be unknown
1266+
self.assertEqual(200, response.status_code)
1267+
1268+
response_obj = json.loads(response.data)
1269+
self.assertIn('samples', response_obj)
1270+
response_obj = response_obj['samples']
1271+
self.assertEqual(len(response_obj), 3)
1272+
1273+
self.assertEqual([v['sampleid'] for v in response_obj], barcodes)
1274+
response_projects = [v['project'].split('; ') for v in response_obj]
1275+
self.assertEqual(len(response_projects[0]), 2)
1276+
self.assertEqual(len(response_projects[1]), 3)
1277+
self.assertEqual(len(response_projects[2]), 3)
1278+
self.assertTrue(all(
1279+
projs[0] == 'American Gut Project' for projs in response_projects
1280+
))
1281+
# content: should have exactly the same projects
1282+
self.assertEqual(response_projects[1], response_projects[2])
1283+
1284+
def test_query_barcode_stats_by_project(self):
1285+
input_json = json.dumps({'project_id': 19}) # expect 5 barcodes
1286+
exp_barcodes = [
1287+
'000035369', '000035370', '000035371',
1288+
'000035372', '000035373'
1289+
]
1290+
1291+
response = self.client.post(
1292+
"api/admin/account_barcode_summary?strip_sampleid=False",
1293+
content_type='application/json',
1294+
data=input_json,
1295+
headers=MOCK_HEADERS
1296+
)
1297+
# an empty string project should be unknown
1298+
self.assertEqual(200, response.status_code)
1299+
1300+
response_obj = json.loads(response.data)
1301+
self.assertIn('samples', response_obj)
1302+
response_obj = response_obj['samples']
1303+
self.assertEqual(len(response_obj), 5)
1304+
self.assertEqual([v['sampleid'] for v in response_obj], exp_barcodes)
1305+
12551306
def test_send_email(self):
12561307
def mock_func(*args, **kwargs):
12571308
pass

microsetta_private_api/api/_removal_queue.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ def check_request_remove_account(account_id, token_info):
1515
return jsonify(result), 200
1616

1717

18-
def request_remove_account(account_id, token_info):
18+
def request_remove_account(account_id, token_info, user_delete_reason=None):
1919
# raises 401 if method fails
2020
_validate_account_access(token_info, account_id)
2121

2222
with Transaction() as t:
2323
rq_repo = RemovalQueueRepo(t)
24-
rq_repo.request_remove_account(account_id)
24+
rq_repo.request_remove_account(account_id, user_delete_reason)
2525
t.commit()
2626

2727
return jsonify(code=200, message="Request Accepted"), 200

microsetta_private_api/api/microsetta_private_api.yaml

+17
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,12 @@ paths:
184184
description: Request account to be removed
185185
parameters:
186186
- $ref: '#/components/parameters/account_id'
187+
- name: user_delete_reason
188+
in: query
189+
description: Reason for account deletion
190+
required: false
191+
schema:
192+
type: string
187193
responses:
188194
'200':
189195
description: Successfully requested for account to be removed
@@ -199,6 +205,12 @@ paths:
199205
description: Cancel request for account to be removed
200206
parameters:
201207
- $ref: '#/components/parameters/account_id'
208+
- name: user_delete_reason
209+
in: query
210+
description: Reason for account deletion
211+
required: false
212+
schema:
213+
type: string
202214
responses:
203215
'200':
204216
description: Successfully canceled request
@@ -2504,6 +2516,11 @@ paths:
25042516
- Admin
25052517
parameters:
25062518
- $ref: '#/components/parameters/account_id'
2519+
- name: delete_reason
2520+
in: query
2521+
required: true
2522+
schema:
2523+
type: string
25072524
responses:
25082525
'200':
25092526
description: Updates queue, log before calling delete_account()

microsetta_private_api/api/tests/test_api.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -1425,6 +1425,7 @@ def test_request_account_removal(self):
14251425
self.assertFalse(json.loads(response.data)['status'])
14261426

14271427
# submit a request for this account to be removed.
1428+
delete_reason = "User requested account removal"
14281429
response = self.client.put(
14291430
f'/api/accounts/{dummy_acct_id}/removal_queue',
14301431
headers=self.dummy_auth)
@@ -1440,8 +1441,9 @@ def test_request_account_removal(self):
14401441
headers=self.dummy_auth)
14411442

14421443
self.assertEqual(200, response.status_code)
1443-
1444-
self.assertTrue(json.loads(response.data)['status'])
1444+
removal_info = json.loads(response.data)
1445+
self.assertTrue(removal_info['status'])
1446+
# self.assertEqual(removal_info['delete_reason'], delete_reason)
14451447

14461448
# try to request a second time. Verify that an error is returned
14471449
# instead.
@@ -1491,7 +1493,8 @@ def test_request_account_removal(self):
14911493
"Request Accepted")
14921494

14931495
response = self.client.put(
1494-
f'/api/admin/account_removal/{dummy_acct_id}',
1496+
f'/api/admin/account_removal/{dummy_acct_id}'
1497+
f'?delete_reason={delete_reason}',
14951498
headers=make_headers(FAKE_TOKEN_ADMIN))
14961499

14971500
self.assertEqual(204, response.status_code)
@@ -1557,7 +1560,8 @@ def test_request_account_removal(self):
15571560
# functionality; it deletes the id from the delete-queue and logs the
15581561
# deletion in a separate 'log' table, before calling account_delete().
15591562
response = self.client.delete(
1560-
f'/api/admin/account_removal/{dummy_acct_id}',
1563+
f'/api/admin/account_removal/{dummy_acct_id}'
1564+
f'?delete_reason={delete_reason}',
15611565
headers=make_headers(FAKE_TOKEN_ADMIN))
15621566

15631567
# confirm that the operation was a success.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- Feb 5, 2024
2+
-- Add delete_reason to ag.account_removal_log
3+
ALTER TABLE ag.account_removal_log
4+
ADD COLUMN delete_reason VARCHAR;
5+
6+
COMMENT ON COLUMN ag.account_removal_log.delete_reason
7+
IS 'Reason the admin gave for deleting the account.';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- Feb 12, 2024
2+
-- Add user_delete_reason to ag.delete_account_queue
3+
ALTER TABLE ag.delete_account_queue
4+
ADD COLUMN user_delete_reason VARCHAR;
5+
6+
COMMENT ON COLUMN ag.delete_account_queue.user_delete_reason
7+
IS 'Reason the user gave for deleting the account.';

microsetta_private_api/model/removal_queue_requests.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
class RemovalQueueRequest(ModelBase):
55
def __init__(self, id, account_id, email, first_name, last_name,
6-
requested_on):
6+
requested_on, user_delete_reason):
77
self.id = id
88
self.account_id = account_id
99
self.email = email
@@ -12,6 +12,7 @@ def __init__(self, id, account_id, email, first_name, last_name,
1212

1313
# 2022-07-27 17:15:33.937458-07:00 -> 2022-07-27 17:15:33
1414
self.requested_on = str(requested_on).split('.')[0]
15+
self.user_delete_reason = user_delete_reason
1516

1617
def to_api(self):
1718
return self.__dict__.copy()

microsetta_private_api/repo/interested_user_repo.py

+44
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ def update_interested_user(self, interested_user):
6666
"state = %s, "
6767
"postal_code = %s, "
6868
"country = %s, "
69+
"latitude = %s, "
70+
"longitude = %s, "
71+
"confirm_consent = %s, "
6972
"address_checked = %s, "
7073
"address_valid = %s, "
7174
"residential_address = %s, "
@@ -82,6 +85,9 @@ def update_interested_user(self, interested_user):
8285
interested_user.state,
8386
interested_user.postal_code,
8487
interested_user.country,
88+
interested_user.latitude,
89+
interested_user.longitude,
90+
interested_user.confirm_consent,
8591
interested_user.address_checked,
8692
interested_user.address_valid,
8793
interested_user.residential_address,
@@ -207,3 +213,41 @@ def verify_address(self, interested_user_id):
207213
(interested_user_id,)
208214
)
209215
return False
216+
217+
def get_interested_user_by_just_email(self, email):
218+
with self._transaction.dict_cursor() as cur:
219+
cur.execute(
220+
"SELECT * FROM campaign.interested_users "
221+
"WHERE lower(email) = lower(%s)",
222+
(email,)
223+
)
224+
rs = cur.fetchall()
225+
return [__class__._row_to_interested_user(r) for r in rs]
226+
227+
def scrub(self, interested_user_email):
228+
interested_users = self. \
229+
get_interested_user_by_just_email(interested_user_email)
230+
231+
for interested_user in interested_users:
232+
interested_user.first_name = "scrubbed"
233+
interested_user.last_name = "scrubbed"
234+
interested_user.email = "scrubbed"
235+
interested_user.phone = "scrubbed"
236+
interested_user.address_1 = "scrubbed"
237+
interested_user.address_2 = "scrubbed"
238+
interested_user.address_3 = "scrubbed"
239+
interested_user.city = "scrubbed"
240+
interested_user.state = "scrubbed"
241+
interested_user.postal_code = "scrubbed"
242+
interested_user.country = "scrubbed"
243+
interested_user.latitude = None
244+
interested_user.longitude = None
245+
interested_user.confirm_consent = False
246+
interested_user.ip_address = "scrubbed"
247+
interested_user.address_checked = False
248+
interested_user.address_valid = False
249+
interested_user.residential_address = False
250+
251+
if not self.update_interested_user(interested_user):
252+
raise RepoException("Error scrubbing interested user: "
253+
+ interested_user.interested_user_id)

0 commit comments

Comments
 (0)